commit 94e92ff8a3119a6b4ae13ad15732497a0a0baaef Author: Yann Esposito (Yogsototh) Date: Mon Mar 4 11:54:08 2013 +0100 initial commit diff --git a/404.html b/404.html new file mode 100644 index 0000000..56c8e0e --- /dev/null +++ b/404.html @@ -0,0 +1,97 @@ + + + + + + YBlog - 404 Error + + + + + + + + + + + + +
+ + +
+

404 Error

+
+
+
+
+

The page you’re looking for is not there. If you want to help me fix this, send me the following content:

+
+404 on + +from + + +
+

using one of the following method

+ +

Thanks!

+
+ +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..c6f0806 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +yannesposito.com diff --git a/Scratch/css/cmu.css b/Scratch/css/cmu.css new file mode 100644 index 0000000..20875c9 --- /dev/null +++ b/Scratch/css/cmu.css @@ -0,0 +1 @@ +pre, code, a.cut{font-family:"cmuntt", Incosolata, Monaco, monospace}body{font-family:"ComputerModern", Georgia, Palatino, "Century Schoolbook L", "Times New Roman", Times, serif} \ No newline at end of file diff --git a/Scratch/css/cmufontface.css b/Scratch/css/cmufontface.css new file mode 100644 index 0000000..4ab9ee6 --- /dev/null +++ b/Scratch/css/cmufontface.css @@ -0,0 +1 @@ +@font-face{font-family:"cmuntt";src:url("fonts/cmuntt.eot");src:local("☺"), url("fonts/cmuntt.svg") format("svg"), url("fonts/cmuntt.ttf") format("truetype");font-weight:normal;font-style:normal}@font-face{font-family:"ComputerModern";src:url("fonts/cmunrb.eot");src:local("☺"), url("fonts/cmunrb.svg") format("svg"), url("fonts/cmunrb.ttf") format("truetype");font-weight:bold}@font-face{font-family:"ComputerModern";src:url("fonts/cmunsl.eot");src:local("☺"), url("fonts/cmunsl.svg") format("svg"), url("fonts/cmunsl.ttf") format("truetype");font-style:italic, oblique}@font-face{font-family:"ComputerModern";src:url("fonts/cmunrm.eot");src:local("☺"), url("fonts/cmunrm.svg") format("svg"), url("fonts/cmunrm.ttf") format("truetype");font-weight:normal;font-style:normal} \ No newline at end of file diff --git a/Scratch/css/darkmodern.css b/Scratch/css/darkmodern.css new file mode 100644 index 0000000..4c3b4aa --- /dev/null +++ b/Scratch/css/darkmodern.css @@ -0,0 +1 @@ + html{padding:0}body{padding:0;margin:0;font-family:"Droid Sans", Helvetica, sans-serif !important;font-size:16px;line-height:24px;background:#073642;color:#93a1a1}a{text-decoration:none;color:#268bd2}a:visited{color:#268bd2}a:hover{color:#cb4b16;text-shadow:0 0 1px #dc322f}.corps a{color:#93a1a1}.corps a:after{content:"*";line-height:0;font-size:0.66em;vertical-align:super}.corps a:visited{color:#93a1a1}#navigation{text-align:center;padding:1em;letter-spacing:0.25em}#navigation .sep{opacity:0.3;font-style:italic}#blackpage, #nojsredirect{top:0;left:0;width:100%;min-height:100%;margin-left:0;margin-right:0;margin-top:0;margin-bottom:0;position:fixed;text-align:center;background:#002b36}#content{background:#001823;color:#93a1a1;width:720px;margin:0 auto;padding:0}#content #titre h1{padding:0 64px;margin:64px auto;text-align:center;font-weight:200}#content h1, #content h2, #content h3, #content h4, #content h5, #content h6{padding:0 64px;margin:64px 0;color:#839496}#content table{margin:16px 0;padding:0 64px}#content table tr:nth-child(odd){background-color:#002b36}#content table tr th{border:solid medium #073642;padding:4px;margin:0}#content table tr td{border:solid 1px #073642;padding:4px;margin:0}#content figure, #content .figure{margin:0;padding:0}#content figure figcaption, #content figure .caption, #content .figure figcaption, #content .figure .caption{padding:0 64px;margin:16px 0}#content p{padding:0 64px;margin:16px 0}#content img{max-width:100%;display:block;border-top:medium solid;border-bottom:medium solid;margin:64px auto}#content pre{background:#002b36;font-family:monaco, monospace;font-size:16px;overflow:auto;padding:16px;line-height:17.92px;border-top:solid 1px #073642;border-bottom:solid 1px #073642}#content pre code{background:none;border:none}#content ul{list-style:none}#content ul li:before{content:"- "}#content ul{padding-left:0;margin:16px 64px;text-indent:-8px}#content ol{padding-left:0;margin:16px 64px}#content .toc a, #content #markdown-toc a{color:#93a1a1}#content .toc ol li, #content .toc ul li, #content #markdown-toc ol li, #content #markdown-toc ul li{margin:8px 0}#content ol li ul, #content ol li ol, #content ul li ol, #content ul li ul{margin:8px 24px;list-style:none}#content li p{display:inline;margin:0;padding:0}#entete > #choix > #choixrss{margin:0;padding:0}#entete > #choix > #choixlang{float:left}#choixlang{float:left}#switchcss{float:right}#choix{text-align:center}#choix > div{display:inline-block}#header{border-bottom:8px solid #073642}#choix{text-align:center;font-size:12px;padding:0 16px;font-weight:bold}#choix #switchcss{float:right}.cut{color:#93a1a1;font-size:10.66667px;opacity:0.5;display:block;text-align:right}.cut:after{content:" »"}.cut:hover{opacity:1}hr{color:#586e75;border-color:#073642;margin:0 64px}p code, li code{padding:1px 2px;background:#002b36;border:solid 1px #073642}#content blockquote{border:solid 1px #073642;background:#002b36}#content blockquote p{padding:0 16px}#content blockquote code{background:#002b36;border:solid 1px rgba(0, 0, 0, 0.1)}#content blockquote pre code{background:none;border:none}#social, #choixrss, #comment{margin:16px 64px}#social{text-align:center;opacity:0.3}#social:hover{opacity:1}#comment img{width:auto;max-width:100%}.intro{width:646px;margin:0 auto;font-size:14px;line-height:21px;color:#839496}.intro blockquote hr{display:none}.left{float:left}.right{float:right}#content img.right, #content img.left{max-width:30%;border:medium solid}#content img.left{margin:0 32px 0 64px}#content img.right{margin:0 64px 0 32px}.flush{clear:both}#bottom{padding:16px 0;text-align:center;font-size:14px;line-height:21px}#entete{padding:16px 0;text-align:center}#entete ul{text-indent:0}#entete ul li:before{content:""}#entete ul li{display:inline-block}#entete ul li span.active{color:#cb4b16}#entete ul li > *{padding:2px 16px;border:solid}#previous_articles{float:left;text-align:left}#next_articles{float:right;text-align:right}.corps{padding-bottom:32px}#tagcloud{margin:16px 64px;font-size:14px;line-height:21px}#sousliens.archive > ul{display:none}#sousliens.archive > h4:hover{cursor:pointer}#hiddenDivs > div{display:none}.list{margin:16px 64px}pre .list{margin:0 0}#content img#mainlogo{width:auto;margin:0 auto;display:block;max-width:100%}.date, .day, .month, .year{display:inline-block;padding-left:10px;text-align:right}.day{width:10px}.month{width:20px}.year{width:30px}.date{margin-right:10px}#content .popularblock{display:block;float:left;margin:1.5%;width:30%}#content .popularblock a:after{content:""}#content .popularblock figure, #content .popularblock .figure{width:100%;overflow:hidden}#content .popularblock figure img, #content .popularblock .figure img{height:120px}#content .popularblock figure figcaption, #content .popularblock figure .caption, #content .popularblock .figure figcaption, #content .popularblock .figure .caption{padding:0;text-align:center}.base03{color:#002b36}.base02{color:#073642}.base01{color:#586e75}.base00{color:#657b83}.base0{color:#839496}.base1{color:#93a1a1}.base2{color:#eee8d5}.base3{color:#fdf6e3}.yellow{color:#b58900}.orange{color:#cb4b16}.red{color:#dc322f}.magenta{color:#d33682}.violet{color:#6c71c4}.blue{color:#268bd2}.cyan{color:#2aa198}.green{color:#859900}#content section.slide{min-height:20em;border-top:1.6px solid #073642;border-bottom:1.6px solid #073642;margin:64px 0;background-color:#002b36;color:#93a1a1;padding:16px 0}#content section.slide > *{padding:0 16px}#content section.slide a{text-decoration:none;color:#839496}#content section.slide a:visited{color:#586e75}#content section.slide pre{padding:16px;margin:16px}#content section.slide pre, #content section.slide code{background-color:#002b36;border:1px solid #073642}#content section.slide pre code, #content section.slide code code{border:none;background:none}#content section.slide blockquote{background-color:#002b36;border-color:#073642}#content section.slide h1, #content section.slide h2, #content section.slide h3, #content section.slide h4, #content section.slide h5, #content section.slide h6{color:#839496}#content section.slide ul{margin:16px 0;padding:0}#content section.slide ul li{margin:8px 24px}#content section.slide img{display:block;max-width:80%;margin:16px auto;padding:8px;background-color:#002b36;border:solid 1px #073642}#content section.slide img.right, #content section.slide img.left, #content section.slide figure.right, #content section.slide figure.left, #content section.slide .figure.right, #content section.slide .figure.left{max-width:26%;margin:8px}#content section.slide img.right figcaption, #content section.slide img.left figcaption, #content section.slide figure.right figcaption, #content section.slide figure.left figcaption, #content section.slide .figure.right figcaption, #content section.slide .figure.left figcaption{padding:0;text-align:center}#content section.slide figure, #content section.slide .figure{max-width:80%;margin:0 auto}#content section.slide figure img, #content section.slide .figure img{max-width:80%}.codefile{font-size:11.2px;text-align:right;margin-bottom:-32px}.footnotes{font-size:12.8px}#next_before_articles{font-size:11.2px;padding:0 64px;margin:16px 0}#content .inlineblockimg{width:60px;margin:0 8px;text-align:center;vertical-align:middle;display:inline-block}#content img.inlineimage{display:inline-block;max-height:48px;max-width:48px;border:solid;box-shadow:none;margin:0 auto;vertical-align:middle;font-size:7px;font-family:Helvetica, sans-serif;overflow:hidden}a.rss{background-color:#f8f8f8;background-image:-moz-linear-gradient(center top, white, #dedede);border:1px solid #cccccc;border-radius:3px 3px 3px 3px;color:#cb4b16;cursor:pointer;display:inline-block;font-size:13px;font-weight:bold;height:20px;font-family:sans-serif;line-height:20px;overflow:hidden;padding:0 5px;position:relative;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);margin:0 20px}a.rss:hover{background-image:-moz-linear-gradient(center top, white, #f3f3f3)} \ No newline at end of file diff --git a/Scratch/css/dynamic.css b/Scratch/css/dynamic.css new file mode 100644 index 0000000..b61510f --- /dev/null +++ b/Scratch/css/dynamic.css @@ -0,0 +1 @@ +*{transition-property:all;transition-duration:0.5s;-moz-transition-property:all;-moz-transition-duration:0.5s;-webkit-transition-property:all;-webkit-transition-duration:0.5s;-o-transition-property:all;-o-transition-duration:0.5s}#social *, #comment *{transition-property:all;transition-duration:0s;-moz-transition-property:all;-moz-transition-duration:0s;-webkit-transition-property:all;-webkit-transition-duration:0s;-o-transition-property:all;-o-transition-duration:0s} \ No newline at end of file diff --git a/Scratch/css/fonts/cmunorm.eot b/Scratch/css/fonts/cmunorm.eot new file mode 100755 index 0000000..c38720c Binary files /dev/null and b/Scratch/css/fonts/cmunorm.eot differ diff --git a/Scratch/css/fonts/cmunorm.otf b/Scratch/css/fonts/cmunorm.otf new file mode 100644 index 0000000..d7310da Binary files /dev/null and b/Scratch/css/fonts/cmunorm.otf differ diff --git a/Scratch/css/fonts/cmunorm.svg b/Scratch/css/fonts/cmunorm.svg new file mode 100755 index 0000000..d7310da Binary files /dev/null and b/Scratch/css/fonts/cmunorm.svg differ diff --git a/Scratch/css/fonts/cmunorm.ttf b/Scratch/css/fonts/cmunorm.ttf new file mode 100755 index 0000000..4153501 Binary files /dev/null and b/Scratch/css/fonts/cmunorm.ttf differ diff --git a/Scratch/css/fonts/cmunorm.woff b/Scratch/css/fonts/cmunorm.woff new file mode 100644 index 0000000..294cc39 Binary files /dev/null and b/Scratch/css/fonts/cmunorm.woff differ diff --git a/Scratch/css/fonts/cmunrb.eot b/Scratch/css/fonts/cmunrb.eot new file mode 100644 index 0000000..70dfff6 Binary files /dev/null and b/Scratch/css/fonts/cmunrb.eot differ diff --git a/Scratch/css/fonts/cmunrb.svg b/Scratch/css/fonts/cmunrb.svg new file mode 100644 index 0000000..dda7ceb Binary files /dev/null and b/Scratch/css/fonts/cmunrb.svg differ diff --git a/Scratch/css/fonts/cmunrb.ttf b/Scratch/css/fonts/cmunrb.ttf new file mode 100644 index 0000000..fdea84f Binary files /dev/null and b/Scratch/css/fonts/cmunrb.ttf differ diff --git a/Scratch/css/fonts/cmunrm.eot b/Scratch/css/fonts/cmunrm.eot new file mode 100755 index 0000000..3a43ac4 Binary files /dev/null and b/Scratch/css/fonts/cmunrm.eot differ diff --git a/Scratch/css/fonts/cmunrm.otf b/Scratch/css/fonts/cmunrm.otf new file mode 100644 index 0000000..b449df0 Binary files /dev/null and b/Scratch/css/fonts/cmunrm.otf differ diff --git a/Scratch/css/fonts/cmunrm.svg b/Scratch/css/fonts/cmunrm.svg new file mode 100755 index 0000000..b449df0 Binary files /dev/null and b/Scratch/css/fonts/cmunrm.svg differ diff --git a/Scratch/css/fonts/cmunrm.ttf b/Scratch/css/fonts/cmunrm.ttf new file mode 100755 index 0000000..fd5f297 Binary files /dev/null and b/Scratch/css/fonts/cmunrm.ttf differ diff --git a/Scratch/css/fonts/cmunrm.woff b/Scratch/css/fonts/cmunrm.woff new file mode 100644 index 0000000..0477fb3 Binary files /dev/null and b/Scratch/css/fonts/cmunrm.woff differ diff --git a/Scratch/css/fonts/cmunsl.eot b/Scratch/css/fonts/cmunsl.eot new file mode 100644 index 0000000..9571c71 Binary files /dev/null and b/Scratch/css/fonts/cmunsl.eot differ diff --git a/Scratch/css/fonts/cmunsl.svg b/Scratch/css/fonts/cmunsl.svg new file mode 100644 index 0000000..05608b2 Binary files /dev/null and b/Scratch/css/fonts/cmunsl.svg differ diff --git a/Scratch/css/fonts/cmunsl.ttf b/Scratch/css/fonts/cmunsl.ttf new file mode 100644 index 0000000..8acf7a3 Binary files /dev/null and b/Scratch/css/fonts/cmunsl.ttf differ diff --git a/Scratch/css/fonts/cmuntt.eot b/Scratch/css/fonts/cmuntt.eot new file mode 100644 index 0000000..0b69987 Binary files /dev/null and b/Scratch/css/fonts/cmuntt.eot differ diff --git a/Scratch/css/fonts/cmuntt.otf b/Scratch/css/fonts/cmuntt.otf new file mode 100644 index 0000000..a564385 Binary files /dev/null and b/Scratch/css/fonts/cmuntt.otf differ diff --git a/Scratch/css/fonts/cmuntt.svg b/Scratch/css/fonts/cmuntt.svg new file mode 100644 index 0000000..a564385 Binary files /dev/null and b/Scratch/css/fonts/cmuntt.svg differ diff --git a/Scratch/css/fonts/cmuntt.ttf b/Scratch/css/fonts/cmuntt.ttf new file mode 100644 index 0000000..1d1cfb3 Binary files /dev/null and b/Scratch/css/fonts/cmuntt.ttf differ diff --git a/Scratch/css/fonts/cmuntt.woff b/Scratch/css/fonts/cmuntt.woff new file mode 100644 index 0000000..127b2e6 Binary files /dev/null and b/Scratch/css/fonts/cmuntt.woff differ diff --git a/Scratch/css/modern.css b/Scratch/css/modern.css new file mode 100644 index 0000000..0e722c0 --- /dev/null +++ b/Scratch/css/modern.css @@ -0,0 +1 @@ + html{padding:0}body{padding:0;margin:0;font-family:"Droid Sans", Helvetica, sans-serif !important;font-size:16px;line-height:24px;background:#93a1a1;color:#586e75}a{text-decoration:none;color:#268bd2}a:visited{color:#268bd2}a:hover{color:#cb4b16;text-shadow:0 0 1px #dc322f}.corps a{color:#586e75}.corps a:after{content:"*";line-height:0;font-size:0.66em;vertical-align:super}.corps a:visited{color:#586e75}#navigation{text-align:center;padding:1em;letter-spacing:0.25em}#navigation .sep{opacity:0.3;font-style:italic}#blackpage, #nojsredirect{top:0;left:0;width:100%;min-height:100%;margin-left:0;margin-right:0;margin-top:0;margin-bottom:0;position:fixed;text-align:center;background:#fdf6e3}#content{background:#fffffb;color:#586e75;width:720px;margin:0 auto;padding:0}#content #titre h1{padding:0 64px;margin:64px auto;text-align:center;font-weight:200}#content h1, #content h2, #content h3, #content h4, #content h5, #content h6{padding:0 64px;margin:64px 0;color:#657b83}#content table{margin:16px 0;padding:0 64px}#content table tr:nth-child(odd){background-color:#fdf6e3}#content table tr th{border:solid medium #eee8d5;padding:4px;margin:0}#content table tr td{border:solid 1px #eee8d5;padding:4px;margin:0}#content figure, #content .figure{margin:0;padding:0}#content figure figcaption, #content figure .caption, #content .figure figcaption, #content .figure .caption{padding:0 64px;margin:16px 0}#content p{padding:0 64px;margin:16px 0}#content img{max-width:100%;display:block;border-top:medium solid;border-bottom:medium solid;margin:64px auto}#content pre{background:#fdf6e3;font-family:monaco, monospace;font-size:16px;overflow:auto;padding:16px;line-height:17.92px;border-top:solid 1px #eee8d5;border-bottom:solid 1px #eee8d5}#content pre code{background:none;border:none}#content ul{list-style:none}#content ul li:before{content:"- "}#content ul{padding-left:0;margin:16px 64px;text-indent:-8px}#content ol{padding-left:0;margin:16px 64px}#content .toc a, #content #markdown-toc a{color:#586e75}#content .toc ol li, #content .toc ul li, #content #markdown-toc ol li, #content #markdown-toc ul li{margin:8px 0}#content ol li ul, #content ol li ol, #content ul li ol, #content ul li ul{margin:8px 24px;list-style:none}#content li p{display:inline;margin:0;padding:0}#entete > #choix > #choixrss{margin:0;padding:0}#entete > #choix > #choixlang{float:left}#choixlang{float:left}#switchcss{float:right}#choix{text-align:center}#choix > div{display:inline-block}#header{border-bottom:8px solid #93a1a1}#choix{text-align:center;font-size:12px;padding:0 16px;font-weight:bold}#choix #switchcss{float:right}.cut{color:#586e75;font-size:10.66667px;opacity:0.5;display:block;text-align:right}.cut:after{content:" »"}.cut:hover{opacity:1}hr{color:#93a1a1;border-color:#eee8d5;margin:0 64px}p code, li code{padding:1px 2px;background:#fdf6e3;border:solid 1px #eee8d5}#content blockquote{border:solid 1px #eee8d5;background:#fdf6e3}#content blockquote p{padding:0 16px}#content blockquote code{background:#fdf6e3;border:solid 1px rgba(0, 0, 0, 0.1)}#content blockquote pre code{background:none;border:none}#social, #choixrss, #comment{margin:16px 64px}#social{text-align:center;opacity:0.3}#social:hover{opacity:1}#comment img{width:auto;max-width:100%}.intro{width:646px;margin:0 auto;font-size:14px;line-height:21px;color:#657b83}.intro blockquote hr{display:none}.left{float:left}.right{float:right}#content img.right, #content img.left{max-width:30%;border:medium solid}#content img.left{margin:0 32px 0 64px}#content img.right{margin:0 64px 0 32px}.flush{clear:both}#bottom{padding:16px 0;text-align:center;font-size:14px;line-height:21px}#entete{padding:16px 0;text-align:center}#entete ul{text-indent:0}#entete ul li:before{content:""}#entete ul li{display:inline-block}#entete ul li span.active{color:#cb4b16}#entete ul li > *{padding:2px 16px;border:solid}#previous_articles{float:left;text-align:left}#next_articles{float:right;text-align:right}.corps{padding-bottom:32px}#tagcloud{margin:16px 64px;font-size:14px;line-height:21px}#sousliens.archive > ul{display:none}#sousliens.archive > h4:hover{cursor:pointer}#hiddenDivs > div{display:none}.list{margin:16px 64px}pre .list{margin:0 0}#content img#mainlogo{width:auto;margin:0 auto;display:block;max-width:100%}.date, .day, .month, .year{display:inline-block;padding-left:10px;text-align:right}.day{width:10px}.month{width:20px}.year{width:30px}.date{margin-right:10px}#content .popularblock{display:block;float:left;margin:1.5%;width:30%}#content .popularblock a:after{content:""}#content .popularblock figure, #content .popularblock .figure{width:100%;overflow:hidden}#content .popularblock figure img, #content .popularblock .figure img{height:120px}#content .popularblock figure figcaption, #content .popularblock figure .caption, #content .popularblock .figure figcaption, #content .popularblock .figure .caption{padding:0;text-align:center}.base03{color:#002b36}.base02{color:#073642}.base01{color:#586e75}.base00{color:#657b83}.base0{color:#839496}.base1{color:#93a1a1}.base2{color:#eee8d5}.base3{color:#fdf6e3}.yellow{color:#b58900}.orange{color:#cb4b16}.red{color:#dc322f}.magenta{color:#d33682}.violet{color:#6c71c4}.blue{color:#268bd2}.cyan{color:#2aa198}.green{color:#859900}#content section.slide{min-height:20em;border-top:1.6px solid #073642;border-bottom:1.6px solid #073642;margin:64px 0;background-color:#fdf6e3;color:#586e75;padding:16px 0}#content section.slide > *{padding:0 16px}#content section.slide a{text-decoration:none;color:#657b83}#content section.slide a:visited{color:#93a1a1}#content section.slide pre{padding:16px;margin:16px}#content section.slide pre, #content section.slide code{background-color:#fdf6e3;border:1px solid #eee8d5}#content section.slide pre code, #content section.slide code code{border:none;background:none}#content section.slide blockquote{background-color:#fdf6e3;border-color:#eee8d5}#content section.slide h1, #content section.slide h2, #content section.slide h3, #content section.slide h4, #content section.slide h5, #content section.slide h6{color:#657b83}#content section.slide ul{margin:16px 0;padding:0}#content section.slide ul li{margin:8px 24px}#content section.slide img{display:block;max-width:80%;margin:16px auto;padding:8px;background-color:#fdf6e3;border:solid 1px #eee8d5}#content section.slide img.right, #content section.slide img.left, #content section.slide figure.right, #content section.slide figure.left, #content section.slide .figure.right, #content section.slide .figure.left{max-width:26%;margin:8px}#content section.slide img.right figcaption, #content section.slide img.left figcaption, #content section.slide figure.right figcaption, #content section.slide figure.left figcaption, #content section.slide .figure.right figcaption, #content section.slide .figure.left figcaption{padding:0;text-align:center}#content section.slide figure, #content section.slide .figure{max-width:80%;margin:0 auto}#content section.slide figure img, #content section.slide .figure img{max-width:80%}.codefile{font-size:11.2px;text-align:right;margin-bottom:-32px}.footnotes{font-size:12.8px}#next_before_articles{font-size:11.2px;padding:0 64px;margin:16px 0}#content .inlineblockimg{width:60px;margin:0 8px;text-align:center;vertical-align:middle;display:inline-block}#content img.inlineimage{display:inline-block;max-height:48px;max-width:48px;border:solid;box-shadow:none;margin:0 auto;vertical-align:middle;font-size:7px;font-family:Helvetica, sans-serif;overflow:hidden}a.rss{background-color:#f8f8f8;background-image:-moz-linear-gradient(center top, white, #dedede);border:1px solid #cccccc;border-radius:3px 3px 3px 3px;color:#cb4b16;cursor:pointer;display:inline-block;font-size:13px;font-weight:bold;height:20px;font-family:sans-serif;line-height:20px;overflow:hidden;padding:0 5px;position:relative;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);margin:0 20px}a.rss:hover{background-image:-moz-linear-gradient(center top, white, #f3f3f3)} \ No newline at end of file diff --git a/Scratch/css/scientific.css b/Scratch/css/scientific.css new file mode 100644 index 0000000..5f5f559 --- /dev/null +++ b/Scratch/css/scientific.css @@ -0,0 +1 @@ + @font-face{font-family:"cmuntt";src:url("fonts/cmuntt.eot");src:local("☺"), url("fonts/cmuntt.svg") format("svg"), url("fonts/cmuntt.ttf") format("truetype");font-weight:normal;font-style:normal}@font-face{font-family:"ComputerModern";src:url("fonts/cmunrb.eot");src:local("☺"), url("fonts/cmunrb.svg") format("svg"), url("fonts/cmunrb.ttf") format("truetype");font-weight:bold}@font-face{font-family:"ComputerModern";src:url("fonts/cmunsl.eot");src:local("☺"), url("fonts/cmunsl.svg") format("svg"), url("fonts/cmunsl.ttf") format("truetype");font-style:italic, oblique}@font-face{font-family:"ComputerModern";src:url("fonts/cmunrm.eot");src:local("☺"), url("fonts/cmunrm.svg") format("svg"), url("fonts/cmunrm.ttf") format("truetype");font-weight:normal;font-style:normal}i, em{font-style:italic}b, strong, h1, h2, h3, h4, h5, h6{font-weight:bold}table.description tr td{border:1px solid #eeeef1}.assombris20{background-color:#eeeef1}body{color:#002b36;background-color:#fafafc}::selection{background:#002b36;color:#93a1a1}::-moz-selection{background:#002b36;color:#93a1a1}pre, code, a.cut{font-family:Incosolata, Monaco, monospace}pre::selection{background:#fdf6e3;color:#586e75}pre *::selection{background:#fdf6e3;color:#586e75}pre::-moz-selection{background:#fdf6e3;color:#586e75}pre *::-moz-selection{background:#fdf6e3;color:#586e75}a:hover{text-shadow:0 0 2px #ffaaaa}a, a:link, a:visited, a:active, a:hover{text-decoration:none;outline:none}a, a:link, a:visited, a:active{color:#002b36}a:hover{color:#cb4b16}hr{color:#eeeef1;border-top:1px solid #eeeef1;border-bottom:none;border-left:none;border-right:none}ul{list-style:none}.corps ul li:before{content:"- "}ul{padding-left:0;margin-left:1.5ex;text-indent:-1.5ex}ol{padding-left:0}.toc ol li, .toc ul li{margin:0.5em 0}ol li ul, ol li ol, ul li ol, ul li ul{margin:0.5em 1.5em;list-style:none}body, h1, h2, h3, h4, #entete, .tagname{text-rendering:optimizelegibility;line-height:1.5em}body{font-family:Georgia, Palatino, "Century Schoolbook L", "Times New Roman", Times, serif}#choix{font-style:italic}#navigation{letter-spacing:0.25em;margin:1em auto 2em;font-size:1.25em;text-align:center;font-weight:bold}#navigation .sep{opacity:0.3;font-style:italic}.article #afterheader{counter-reset:niv02}.article #afterheader h2{counter-increment:niv02;counter-reset:niv03;marker-offset:3em}.article #afterheader h2:before{content:counter(niv02) ". "}.article #afterheader h3{counter-increment:niv03;counter-reset:niv04}.article #afterheader h3:before{content:counter(niv02) "." counter(niv03) ". "}.article #afterheader h4{counter-increment:niv04}.article #afterheader h4:before{content:counter(niv02) "." counter(niv03) "." counter(niv04) ". "}pre{background-color:#002b36;color:#839496;box-shadow:0 0 5px #d0d0d2 inset;border-radius:3px;padding:1em;line-height:1.1em;font-size:0.9em}p{margin-bottom:1.2em}.corps blockquote{border:solid 1px #ccccd0;border-radius:2px;box-shadow:0 0 4px #f2f2f4 inset;background-color:#f8f8fa;font-style:italic;padding:0.5em 1em;color:#555566}.corps blockquote ul{margin-left:0}.corps blockquote a:hover{color:#cb4b16}.corps blockquote i, .corps blockquote em{font-weight:normal;font-style:normal;color:#002b36}.corps blockquote strong, .corps blockquote b{font-weight:bold;color:#002b36}.corps blockquote > ul{padding-left:1.5em}abbr, acronym{text-transform:uppercase;font-size:0.8em;text-decoration:none;border-bottom-width:0}#titre{letter-spacing:-0.06em;border-bottom:4px double #ccccd0;border-top:4px double #ccccd0}#liens .active, #sousliens{color:#002b36;border:#ccccd0 solid 1px;border-radius:5px;box-shadow:0 0 2px #ccccd0 inset;background-color:#eeeef1}#liens .active a, #sousliens a{color:#666677}#liens .active a:hover, #sousliens a:hover{color:#cb4b16}#liens .active a:hover strong, #liens .active a:hover b, #liens .active a:hover i, #liens .active a:hover em, #liens .active a:hover .nicer, #sousliens a:hover strong, #sousliens a:hover b, #sousliens a:hover i, #sousliens a:hover em, #sousliens a:hover .nicer{color:#ffb17c}#liens .active hr, #sousliens hr{color:#666677;border-top:1px solid #666677}#liens .active strong, #liens .active b, #liens .active i, #liens .active em, #sousliens strong, #sousliens b, #sousliens i, #sousliens em{color:#002b36}#liens a{border:1px solid #eeeeee;background:rgba(0, 0, 0, 0.05);box-shadow:0 0 2px white, 0 0 3px #cccccc inset;border:1px solid rgba(0, 0, 0, 0.1);border-radius:3px}#liens a:hover{background:rgba(0, 0, 0, 0.1);box-shadow:0 0 6px #555555 inset}#liens .active{text-shadow:0 0 2px rgba(0, 0, 0, 0.5);background-color:#f7f7f9;border:1px solid #e9e9eb;box-shadow:0 0 3px #c7c7c9 inset;border-radius:3px;border-top:none}#lastmod{font-size:0.9em}.nojsbutton{font-size:2.5em}#clickcomment, #choixrss > a{display:block;width:20%;cursor:pointer;margin:1em 0;padding:1em;font-size:16px;line-height:1.4em;border:1px solid #fafafc;color:#ccccd0}#clickcomment:hover, #choixrss > a:hover{border:solid 1px #ccccd0;border-radius:2px;box-shadow:0 0 4px #f2f2f4 inset;background-color:#f8f8fa;color:#dc5c27;text-shadow:0 0 2px #ffaaaa}#clickcomment:hover ul, #choixrss > a:hover ul{margin-left:0}#clickcomment:active, #choixrss > a:active{border:solid 1px #ccccd0;border-radius:2px;box-shadow:0 0 4px #f2f2f4 inset;background-color:#f8f8fa;color:#dc5c27;text-shadow:0 0 2px #ffaaaa;background:#f4f4f6}#clickcomment:active ul, #choixrss > a:active ul{margin-left:0}.return > a, #choixrss > a{float:right}#choix .return > a, #choix #choixrss > a{margin-top:0}#choix #switchcss{float:right}#choix #choixlang{float:left}#choix #choixlang a, #choix #switchcss a{margin-top:0;width:100%}.small{font-size:0.8em}.sc{text-transform:uppercase;font-size:0.8em}.impact, .darkimpact{font-size:2em;margin:0 auto 1em auto;line-height:1.3em}h1 > .date{font-size:0.6em;color:#002b36}.date{font-size:0.8em;color:#fafafc;border:1px solid #002b36;text-align:center;width:4.1em;line-height:1.5em;display:inline-block;vertical-align:middle;margin-right:1em}.date .day, .date .month, .date .year{display:block}.date .day{color:#002b36;background-color:#fafafc;float:left;width:1.7em}.date .month{float:right;width:2.3em;background-color:#002b36;color:#fafafc}.date .year{line-height:3ex;clear:both;color:#002b36;border:#ccccd0 solid 1px;border-radius:5px;box-shadow:0 0 2px #ccccd0 inset;background-color:#eeeef1;border-radius:0}.date .year a{color:#666677}.date .year a:hover{color:#cb4b16}.date .year a:hover strong, .date .year a:hover b, .date .year a:hover i, .date .year a:hover em, .date .year a:hover .nicer{color:#ffb17c}.date .year hr{color:#666677;border-top:1px solid #666677}.date .year strong, .date .year b, .date .year i, .date .year em{color:#002b36}body{text-align:center;font-size:16px}body > #entete{position:absolute;left:0;top:0.5em;width:100%;min-width:45em;z-index:8000;padding-bottom:1em;margin-bottom:3em}#titre h2{width:80%;margin-left:auto;margin-right:auto;text-align:center;color:#ccccd0}#titre{text-align:center;width:100%}#titre h1, #titre h2{padding-left:1em;padding-right:1em}#bottom{clear:right;margin-right:0px;padding:1.5em;line-height:1.5em;color:#224d58;margin-top:2em;text-align:center}#bottom a{color:#113c47}#bottom a:hover{color:#cb4b16}#sousliens{padding:1em 0;line-height:2em}#sousliens ul{list-style:none;margin-left:4em}ul.horizontal li{display:inline;font-size:0.9em}ul.horizontal{margin-top:0px;margin-bottom:0px}#entete{padding-top:0.1em;border-top:1px solid #ccccd0;border-bottom:1px solid #ccccd0}#liens{width:100%;padding:0;clear:both;margin-top:0.5em}#liens ul{width:100%;clear:both;padding:0;margin:0}#liens ul li{display:inline-block;height:4em;margin-left:0.2em;margin-right:0.2em;width:23%}#liens ul li a, #liens ul li span{width:100%;display:block;line-height:4em}.clear{clear:both}#content{line-height:2em;margin-left:auto;margin-right:auto;margin-top:0;position:relative;clear:both;width:47em}.encadre, .black, .intro, .resume, .shadow{padding:2em;margin-top:2em;margin-bottom:2em}.encadre, .black, .shadow{color:#002b36;border:#ccccd0 solid 1px;border-radius:5px;box-shadow:0 0 2px #ccccd0 inset;background-color:#eeeef1}.encadre a, .black a, .shadow a{color:#666677}.encadre a:hover, .black a:hover, .shadow a:hover{color:#cb4b16}.encadre a:hover strong, .encadre a:hover b, .encadre a:hover i, .encadre a:hover em, .encadre a:hover .nicer, .black a:hover strong, .black a:hover b, .black a:hover i, .black a:hover em, .black a:hover .nicer, .shadow a:hover strong, .shadow a:hover b, .shadow a:hover i, .shadow a:hover em, .shadow a:hover .nicer{color:#ffb17c}.encadre hr, .black hr, .shadow hr{color:#666677;border-top:1px solid #666677}.encadre strong, .encadre b, .encadre i, .encadre em, .black strong, .black b, .black i, .black em, .shadow strong, .shadow b, .shadow i, .shadow em{color:#002b36}.intro, .resume{font-size:0.9em;font-style:italic;padding:0.5em 1em;color:#555566}.intro a:hover, .resume a:hover{color:#cb4b16}.intro i, .intro em, .resume i, .resume em{font-weight:normal;font-style:normal;color:#002b36}.intro strong, .intro b, .resume strong, .resume b{font-weight:bold;color:#002b36}#afterheader > h1{width:100%;padding-top:1.5em;text-align:left}#afterheader{padding-left:0em;padding-right:0em}#sousliens{margin-top:3em;margin-bottom:3em;font-size:1.2em;letter-spacing:1px;text-align:left;clear:both}.twilight{line-height:1.1em}.corps{font-size:1.25em;line-height:1.5em;text-align:justify;padding:3em 3em;margin:0 0;clear:both}.corps p, .corps ol, .corps ul, .corps blockquote, .corps pre, .corps code{margin-top:1.5em;margin-bottom:1.5em}.corps pre code{margin:0}.corps p, .corps ol, .corps ul, .corps blockquote{line-height:1.5em}.corps ul li ul, .corps ol li ul, .corps ul li ol, .corps ol li ol{margin-top:0;margin-bottom:0}.corps img{max-width:80%;border:1px solid #ccccd0;background-color:#fafafc;padding:0.5em;box-shadow:0 10px 15px #cccccc;border-radius:3px}.corps a:hover img{background-color:#dc3a05}figure, .figure{margin:3em 0}figure img, .figure img{box-shadow:0 10px 15px #cccccc inset}figure figcaption, figure .caption, .figure figcaption, .figure .caption{text-align:center;margin:0.5em 0}figure.left, figure.right, .figure.right, .figure.left{max-width:30%}img.clean{border:none;background-color:none;box-shadow:none}#address{clear:both}.definitionCell{width:5em;vertical-align:top;text-align:center;font-weight:bold}.valueCell{text-align:right}.smallblock{float:left;width:50%;font-size:1em;font-weight:bold}.largeblock{float:right;width:70%;font-size:1em}#blackpage, #nojsredirect{top:0;left:0;width:100%;min-height:100%;margin-left:0;margin-right:0;margin-top:0;margin-bottom:0;position:absolute;text-align:center}#blackpage{color:#002b36;background-color:#fafafc;font-family:Georgia, serif;font-style:italic;padding-top:8em;z-index:9000;cursor:wait}#blackpage .corps code, #blackpage .corps pre{font-family:monospace}#blackpage img{background:none;border:none;max-width:80%;margin:0 auto}#blackpage a{cursor:pointer}#blackpage .preh1{font-size:1.5em;font-weight:bold;margin-bottom:1em}#blackpage .preh2{font-size:1.2em;font-style:italic;margin-bottom:1em}#blackpage .preintro{text-align:left;width:47em;margin:0 auto}#nojsredirect{z-index:9001}.nojsbutton{width:50%;padding:1em;border:solid 3px white;margin-left:auto;margin-right:auto;margin-top:2em;z-index:9002}.codefile{font-size:0.8em;text-align:right;padding-right:1em;margin-right:0.1;margin-bottom:-1em}.flush{clear:both}table.description{border-spacing:5px;border-collapse:separate;margin-right:auto;margin-left:auto}table.description tr td{padding-left:0.5em;padding-right:0.5em;padding-top:0.5ex;padding-bottom:0.5ex;vertical-align:middle;margin-right:5px}ul.long li{margin-bottom:1em}img{display:block;margin:1.2em auto;background:none;border:none}img.right{max-width:30%;margin-top:0.6em;margin-left:2em}img.left{float:left;max-width:30%;margin-top:0.6em;margin-right:2em}img.inside{display:inline;vertical-align:middle}pre{overflow-x:auto;overflow-y:hidden}.impact, .darkimpact{text-align:left;width:66%;padding-left:0.25em;padding-right:0.25em}table.impact{text-align:left}table.impact tr td{padding-left:0.25em;padding-right:0.25em}#liens{font-size:1.2em}#iemessage{font-size:1.2em;color:#cccccc;margin:-10px;padding:1px 0;background:#333333}#iemessage strong, #iemessage b, #iemessage i, #iemessage em{color:#cccccc}#iemessage a, #iemessage a:visited{color:#eeccaa}.tagname{display:inline;cursor:pointer;margin-left:0.5em;margin-right:0.5em}.list{margin-top:3em}#menuMessage{font-size:1.2em;line-height:1.5em;width:100%;text-align:center}#next_before_articles{clear:both;width:100%;font-size:1.2em;padding-top:1em;padding-bottom:1em}#previous_articles, #next_articles{color:#888899;font-size:0.8em;font-style:italic}#previous_articles{float:left;margin-left:1em;width:45%;text-align:left}.previous_article, .next_article{margin-top:1em}#next_articles{float:right;width:45%;margin-right:1em;text-align:right}#rss{font-size:1.2em;text-align:center;display:block;width:100%;float:right;padding:1em 0.1em}.corps .return a{color:#eeeef1;padding:0.1em;line-height:1.5em;font-size:1.5em;height:1.5em;float:left;font-size:2em;margin-top:-0.5em;margin-left:-2em;width:1.5em}a.return{color:#eeeef1;padding:0.1em;line-height:1.5em;font-size:1.5em;height:1.5em;font-size:2em;width:1.5em;display:block}a.return:hover{color:#888899}.corps .return a:hover{color:#cb4b16}.footnotes{font-size:0.8em}.footnotes ol{color:#839496;font-weight:bold}.footnotes ol p{color:#002b36;font-weight:normal;font-style:normal}.fontnotes ol{margin-left:0}.typeset img{display:inline;border:none;margin:0;padding:0}strong, b, i, em{color:#888899}strong a, b a, i a, em a{color:#002b36}strong a:hover, b a:hover, i a:hover, em a:hover{color:#cb4b16}.corps p strong, .corps p b, .corps p i, .corps p em{color:#555566}a:hover strong, a:hover b, a:hover i, a:hover em{color:#dc5c27}a:hover .nicer{color:#ffb17c}.nicer{color:#ccccd0;font-family:"Lucida Grande", Tahoma}.block{border:solid 1px #ccccd0;border-radius:2px;box-shadow:0 0 4px #f2f2f4 inset;background-color:#f8f8fa;width:26.5%;padding:1em;border-radius:2px;text-align:left;line-height:1em;margin-left:1%;margin-right:1%;font-size:0.8em;height:9em}.block ul{margin-left:0}.block a{color:#002b36}.block a:hover{color:#cb4b16}.block h3{margin:0;font-size:1.3em}.block p{line-height:1.2em}.left{float:left}.right{float:right}.corps p a, .corps ul a{color:#555566}.corps p a:hover, .corps ul a:hover{color:#cb4b16}ul.bloglist, .archive ul{list-style-type:none;margin:0}ul.bloglist li, .archive ul li{margin-bottom:1em}.button{cursor:pointer;text-align:center}#tagcloud{font-size:0.8em;background:#f2f2f4;box-shadow:0 0 6px #ccccd0;border-radius:3px;line-height:2.5em;padding:2em;text-align:justify}.pala{font-family:Palatino}sup{vertical-align:top;font-size:0.7em}.article .corps a:after{content:"†";vertical-align:super;line-height:0;font-size:0.66em;color:#888899}.article .corps .footnotes a:after, .article .corps sup a:after{content:""}.article .corps sup a{font-weight:bold;padding:0 0.3em;margin-left:2px;border-radius:3px;-moz-border-radius:3px;-webkit-border-radius:3px}.article .corps sup a:hover{color:#cb4b16}ul#markdown-toc, .intro .toc ul{text-transform:uppercase;font-size:0.8em;list-style:none;padding-left:1.5em}ul#markdown-toc a:after, .intro .toc ul a:after{content:""}ul#markdown-toc ul ul, .intro .toc ul ul ul{font-variant:normal;line-height:1em;font-size:1em;margin-bottom:1em}table{border:1px solid #ccccd0}table tr td{padding:2px 0.5em}table tr:nth-child(odd){background-color:#f2f2f4}table tr:nth-child(even){background-color:#fafafc}p pre code, ul li pre code, ol li pre code{background:none;border:none;padding:0}p code, ul li code, ol li code{background:#f0f0f2;border:solid 1px #ccccd0;padding:2px}ul.sameline{list-style:none}ul.sameline li{float:left;margin-left:0.5em}.resumearticle{background-color:#f2f2f4;border-radius:7px;box-shadow:0 0 5px #c7c7b8 inset, 0 0 5px white;margin:1em 0;padding:1em}a.cut{font-size:12px;text-align:right;display:block;width:100%;opacity:0.5;border:1px solid #fafafc;border-radius:3px}a.cut:hover{opacity:1;background-color:#f2f2f4;border-color:#ccccd0;box-shadow:0 0 3px #ccccd0 inset}a.cut strong{font-weight:bold}.codehighlight pre{border-left:4px solid #ccccd0}#social{text-align:center;opacity:0.3}#social:hover{opacity:1}.popularblock{width:30.3333%;margin:0 1.5%;float:left}.popularblock figure{margin:0}.popularblock figure img{max-width:80%;max-height:6em}.inlineblockimg{width:48px;margin:0 8px;text-align:center;vertical-align:middle;display:inline-block;line-height:0.8em;overflow:hidden;border:solid 1px}img.inlineimage{padding:0;max-height:48px;max-width:48px;border:none;box-shadow:none;margin:0 auto;vertical-align:middle;font-size:7px;font-family:Helvetica, sans-serif;overflow:hidden}section.slide{border-color:#ccccd0;border:solid 1px;margin-bottom:1em;padding:0.5em;font-family:sans-serif;font-size:0.8em;min-height:25em}a.rss{background-color:#f8f8f8;background-image:-moz-linear-gradient(center top, white, #dedede);border:1px solid #cccccc;border-radius:3px 3px 3px 3px;color:#cb4b16;cursor:pointer;display:inline-block;font-size:13px;font-weight:bold;height:20px;font-family:sans-serif;line-height:20px;overflow:hidden;padding:0 5px;position:relative;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);margin:0 20px}a.rss:hover{background-image:-moz-linear-gradient(center top, white, #f3f3f3)}.base03{color:#002b36}.base02{color:#073642}.base01{color:#586e75}.base00{color:#657b83}.base0{color:#839496}.base1{color:#93a1a1}.base2{color:#eee8d5}.base3{color:#fdf6e3}.yellow{color:#b58900}.orange{color:#cb4b16}.red{color:#dc322f}.magenta{color:#d33682}.violet{color:#6c71c4}.blue{color:#268bd2}.cyan{color:#2aa198}.green{color:#859900}#header{opacity:0.25}#header:hover{opacity:1} \ No newline at end of file diff --git a/Scratch/css/solarized.css b/Scratch/css/solarized.css new file mode 100644 index 0000000..b344d2b --- /dev/null +++ b/Scratch/css/solarized.css @@ -0,0 +1 @@ + pre{padding:0.8em;background:#f3f4f5;color:#657b83;color:#839496;display:block}pre .high0{color:#586e75}pre .high, pre .high1{color:#073642}pre .high2{color:#002b36}pre .DiffInserted, pre .DiffChanged, pre .DiffHeader, pre .DiffDeleted, pre .EmbeddedSource, pre .EmbeddedSourceBright{color:#839496}pre .DiffHeader{font-style:italic}pre .EmbeddedSource, pre .EmbeddedSourceBright{background-color:#073642}pre .low, pre .line-numbers, pre .DoctypeXmlProcessing{color:#586e75}pre .Comment{color:#586e75;font-style:italic}pre .yellow, pre .CssClass, pre .CssPropertyName, pre .Entity, pre .MarkupList{color:#b58900}pre .EntityInheritedClass{color:#b58900;font-style:italic}pre .orange, pre .String, pre .StringRegexp, pre .StringEmbeddedSource, pre .StringConstant, pre .MetaTagAll{color:#cb4b16}pre .red, pre .InvalidIllegal, pre .CssAtRule, pre .InvalidDeprecated{color:#dc322f;font-style:italic}pre .magenta, pre .CCCPreprocessorLine, pre .CCCPreprocessorDirective{color:#d33682}pre .violet, pre .Constant{color:#6c71c4}pre .blue, pre .Storage, pre .Variable, pre .CssId, pre .SupportFunction, pre .MetaTagInline, pre .StringRegexpSpecial, pre .CssTagName, pre .StringVariable, pre .Support{color:#268bd2}pre .cyan, pre .MarkupHeading, pre .CssAdditionalConstants, pre .CssPropertyValue, pre .SupportConstant{color:#2aa198}pre .green, pre .CssPseudoClass, pre .Keyword, pre .CssConstructorArgument{color:#859900}pre code{color:#657b83;background-color:#f3f4f5}pre .comment, pre .template_comment, pre .diff .header, pre .doctype, pre .lisp .string, pre .javadoc{color:#93a1a1;font-style:italic}pre .keyword, pre .css .rule .keyword, pre .winutils, pre .javascript .title, pre .method, pre .addition, pre .css .tag, pre .lisp .title{color:#859900}pre .number, pre .command, pre .string, pre .tag .value, pre .phpdoc, pre .tex .formula, pre .regexp, pre .hexcolor{color:#2aa198}pre .title, pre .localvars, pre .function .title, pre .chunk, pre .decorator, pre .builtin, pre .built_in, pre .lisp .title, pre .identifier, pre .title .keymethods, pre .id{color:#268bd2}pre .attribute, pre .variable, pre .instancevar, pre .lisp .body, pre .smalltalk .number, pre .constant, pre .class .title, pre .parent, pre .haskell .label{color:#b58900}pre .preprocessor, pre .pi, pre .shebang, pre .symbol, pre .diff .change, pre .special, pre .keymethods, pre .attr_selector, pre .important, pre .subst, pre .cdata{color:#cb4b16}pre .deletion{color:#dc322f}pre .tex .formula{background:#eee8d5} \ No newline at end of file diff --git a/Scratch/en/about/contact/index.html b/Scratch/en/about/contact/index.html new file mode 100644 index 0000000..2cd27d1 --- /dev/null +++ b/Scratch/en/about/contact/index.html @@ -0,0 +1,92 @@ + + + + + + YBlog - Contact + + + + + + + + + + + + +
+ + +
+

Contact

+
+
+
+ + +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/about/index.html b/Scratch/en/about/index.html new file mode 100644 index 0000000..3e980f4 --- /dev/null +++ b/Scratch/en/about/index.html @@ -0,0 +1,135 @@ + + + + + + YBlog - About + + + + + + + + + + + + +
+ + +
+

About

+
+
+
+
+
+ +
+
+I look like this

I look like this

+
+ + + + + + + + + + + + + + + + + + + +
NameYann Esposito
EducationPost Ph. D. in Computer Science
SchoolUniversité de Provence
JobIT at Sophia Antipolis (France)
+

Books I like:

+
    +
  • Goëdel, Escher & Bach [Hofstadter]
  • +
  • On Numbers And Games [Conway]
  • +
  • Baudolino [Eco]
  • +
+

Movies I like:

+
    +
  • Eraserhead [Lynch]
  • +
  • Mullholland Drive [Lynch]
  • +
  • Naked Lunch [Cronenberg]
  • +
  • eXistenZ [Cronenberg]
  • +
+

My online bookmarks on pinboard

+

My resume

+

Shortly

+

I’m a passionate guy. Passionate about

+ +

But before all, I love to learn. For example, I learned many programming languages: C, C++, Objective-C, Python, Java, Perl, awk, bash, zsh, LaTeX, Metapost, camL, Haskell

+
+ +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/about/old/index.html b/Scratch/en/about/old/index.html new file mode 100644 index 0000000..a65e7ff --- /dev/null +++ b/Scratch/en/about/old/index.html @@ -0,0 +1,94 @@ + + + + + + YBlog - Other websites + + + + + + + + + + + + +
+ + +
+

Other websites

+
+
+
+ + +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/about/technical_details/index.html b/Scratch/en/about/technical_details/index.html new file mode 100644 index 0000000..50bb830 --- /dev/null +++ b/Scratch/en/about/technical_details/index.html @@ -0,0 +1,93 @@ + + + + + + YBlog - Technical details + + + + + + + + + + + + +
+ + +
+

Technical details

+
+
+
+
+
+ +
+

This website was completely made from scratch. Most is done using Vim and I generate pages using nanoc.

+

Pictures were done with Inkscape and Gimp. My website is versionned using the DCVS Git.

+

Blog comments are externalized to disqus intense debate. All I need is a static web server, no PHP, Java, ASP or CGI. Main advantages of this method concerns the load and the security of the server.

+

If you didn’t understood anything, just remember I used only Opensource and I mostly all done myself from scratch.

+
+ +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/01_nanoc/index.html b/Scratch/en/blog/01_nanoc/index.html new file mode 100644 index 0000000..54666f4 --- /dev/null +++ b/Scratch/en/blog/01_nanoc/index.html @@ -0,0 +1,127 @@ + + + + + + YBlog - Nanoc + + + + + + + + + + + + +
+ + +
+

Nanoc

+
+
+
+
+

What is nanoc?

+

It is not exactly a CMS. But a Framework to generate static web pages.

+

You have to program yourself webpages, the code to generate the menu…

+

I added feature to make my website multilingual for example

+

You’ll can find many informations on the official nanoc website.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2008-10-10 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/02_ackgrep/code/ack b/Scratch/en/blog/02_ackgrep/code/ack new file mode 100644 index 0000000..8b80a5e --- /dev/null +++ b/Scratch/en/blog/02_ackgrep/code/ack @@ -0,0 +1,13 @@ +#!/usr/bin/env zsh +(($#<1)) && { print 'usage: ack "regexp"' >&2; exit 1 } + +listeFic=( **/*(.) ) +autoload zargs +zargs -- $listeFic -- grep $1 | perl -ne 'use Term::ANSIColor; +if (m/([^:]*)(:.*)('$1')(.*)/) { + print color("green").$1; + print color("reset").$2; + print color("black","on_yellow").$3; + print color("reset").$4."\n"; +} ' + \ No newline at end of file diff --git a/Scratch/en/blog/02_ackgrep/index.html b/Scratch/en/blog/02_ackgrep/index.html new file mode 100644 index 0000000..6400652 --- /dev/null +++ b/Scratch/en/blog/02_ackgrep/index.html @@ -0,0 +1,142 @@ + + + + + + YBlog - Better than Grep + + + + + + + + + + + + +
+ + +
+

Better than Grep

+
+
+
+
+

update

+

As Andy Lester told me ack is a simple file you only have to copy in your ~/bin folder. Now I’ve got ack on my professional server.

+

Go on http://betterthangrep.com to download it.

+

Sincerely, I don’t understand ack don’t become a common command on all UNIX systems. I can no more live without. For me it is as essential as which or find.

+
+

Better than grep

+

One of the my main usage of grep is

+
+ + grep ‘pattern’ */(.) +
+ +

Most of time it is enough. But it is far better with colored output. ack-grep in Ubuntu does that. As I couldn’t install it on my ‘Evil Company Server’, I had done one myself in very few lines:

+
+ +

#!/usr/bin/env zsh (($#<1)) && { print ‘usage: ack “regexp”’ >&2; exit 1 }

+listeFic=( /(.) ) autoload zargs zargs – $listeFic – grep $1 | perl -ne ‘use Term::ANSIColor; if (m/([^:])(:.)(’$1’)(.)/) { print color(“green”).$1; print color(“reset”).$2; print color(“black”,“on_yellow”).$3; print color(“reset”).$4.“”; } ’ +
+ +

For my team and I it is usable enough. I hope it could help.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-07-22 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/03_losthighway/index.html b/Scratch/en/blog/03_losthighway/index.html new file mode 100644 index 0000000..01718c5 --- /dev/null +++ b/Scratch/en/blog/03_losthighway/index.html @@ -0,0 +1,199 @@ + + + + + + YBlog - A try to demystify 'Lost Highway' + + + + + + + + + + + + + +
+ + +
+

A try to demystify 'Lost Highway'

+
+
+
+
+

Lost Highway

+
+…this movie must be watched knowing you’ll cannot resolve the solution. At his best you’ll can suggest an interpretation close to the one of David Lynch.
I believe I had found a coherent interpretation which allow to follow the movie without being totally lost. I believed it can give the keys necessary to make its own idea of the movie… +
+ +

Lost Higway is a really good movie. You keep watching it event it seem totally obscure. This is one of the strength of David Lynch.

+

The first time I watched Lost Highway, I was a bit lost. Here some of explanations of Lost Highway I found on the Internet:

+
    +
  • Fred make a pact with the devil incarnated by the Mysterious Man,
  • +
  • Mysterious Man is a video camera,
  • +
  • Just the first part of the story is real. The rest is in the Fred’s imagination,
  • +
+

and I don’t speak about many point of view found in forums.

+

I finished to find two good site talking about this movie. But none of them still totally convinced me:

+
    +
  • the first is mediacircus,
  • +
  • the second which state almost the same interpretation about the movie and explain with even more details is on jasonweb
  • +
+

Nonetheless, this movie must be watched knowing you’ll cannot resolve the solution. At his best you’ll can suggest an interpretation close to the one of David Lynch.

+

I believe I had found a coherent interpretation which allow to follow the movie without being totally lost. I believed it can give the keys necessary to make its own idea of the movie.

+

The Rorschach test

+

test de Rorschach

+

Like the protagonist, everybody see what he want to see in this movie. It is an invitation to think. Watch this movie is a little like watch a Rorschach’s test. What do we see in it? Everybody put its own personnality in the interpretation of the movie.

+
    +
  • If you are mystic, you’ll see in the mysterious man a devil,
  • +
  • If you are more psychanalytics, you’ll see an inconscient part of the protagonist…
  • +
+

Generally, we stay in this movie and we fail explaining everything. There is almost always a point that don’t fit within the interpretation of the movie. This is why trying to find a unique good interpretation of this movie is a mistake.

+

Interprétation ≠ Explanation

+

I give an interpretation and not an explanation. Just to tell my vision of the movie should be very different from yours. There is certainly many coherent explanations.

+

I write this post because I believe I had found an interpretation which seems coherent for most of the movie.

+

Movie’s keys

+
+ + All is in Fred’s memory +
+ +

In a first, it is clear for me, it is not a fantastic movie. If you follow this line, you’ll face many problem explaining some scenes.

+

My hypothesis is the movie describe the Fred’s representation of reality. Each of his tries to escape reality will fail.

+

Fred had commited an horrible act, a murder, and try to repair his memory to accepts it. He’ll then create alternative realities.

+
    +
  • In a first time he kills his wife (Renee) because he believes she cheated at him.
  • +
  • In the second part, he’s weaker and will be manipulated by the blond equivalent of Renee to kill Dick Laurent.
  • +
  • In a third part, he kills Dick Laurent
  • +
+

Why this interpretation can be valid?

+

Because of the dialog at the begining of the movie. Cops ask Fred if he’s own a video camera:

+
+

“Do you own a video camera?”
“No, Fred hates them.”
“I like to remember things my own way.”
“What do you mean by that?”
“How I remember them, not necessarily the way they happened.”

+
+

Then, what we see is not reality but the Fred’s perception. Fred is the God of the reality we see. This is why some God/Devil interpretation of the movie works not so bad.

+

Who is the mysterious man?

+

+

Who’s this mysterious man? He tells Fred it’s him who invited him in his house. He’s present at the party and in the house of Fred in the same time. Eyes wide open, looking everything Fred’s doing?

+

It’s a key of the movie. In my humble opinion, I believe it represents the bad part of Fred. Certainly jalousy. If I was catholic, I’ll said he’s Satan. He observe, film but don’t act. He helps Fred to kill Dick Laurent. Fred had let him enter and cannot let him go. As Iago of Shakespeare is imprisonned by its own jalousy. The Mysterious Man help Fred doing the acts of violence. It also force Fred to remember the reality.

+

When he makes love to his wife (Renee), he sees the face of the Mysterious Man instead of his wife’s face. In reality, it’s the same person for Fred. It should be her who’s the origin of his interior badness.

+

Who’s at the origin of the video tapes?

+

Certainly it’s the mysterious man (Fred himself) who makes them. Their reason should be:

+
    +
  • Remember the reality to Fred. From Fred point-of-view, video tapes are the reality. He tries to forget reality. But, finally, the video tapes go to the end: the murder of his wife.
  • +
  • It may also be a reference to pornographic video tapes, made by Renee.
  • +
+

What really happened?

+

There is many possibilities here. But we have many indices. Here is a supposition.

+

#1 Hypothesis

+

The protagonist is a garagist fallen in love with a porno actress. He believe the producer is the bad guy who go again his will. Then he kills Dick Laurent.

+

#2 Hypothesis

+

He was really married, he had killed his wife. The the remorse let him create an alternate self, which live in a kind of perfect world. But after the time pass, his obsession about the murder came again. And nobody could know if he had killed Andy or not.

+

which one then?

+

The second hypothesis seems better. We can make much more interpretation with it. It explain in most part the strange phone call from Dick Laurent to Pete. But the first hypothesis remain coherent. And, we should probably make an in depth explanantion using the first hypothesis. And I’m not sure it would be better.

+

One of the strength of this movie is to understand there is many other coherent hypothesis. It is an expression of the Rashomon effect. Many different persons could describe in a coherent manner what they saw. But each description contradicts the others.

+
+

Conclusion

+

There is much to tell about this movie. But I believe I put all essential keys here. It is a proof this movie is not a random one.

+

I believe it is essential to remember the “test of Rorschach effet” when watching this movie.

+

I’d like to know or opinion ; is my interpration wrong?

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-08-04 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/04_drm/index.html b/Scratch/en/blog/04_drm/index.html new file mode 100644 index 0000000..a60d68c --- /dev/null +++ b/Scratch/en/blog/04_drm/index.html @@ -0,0 +1,138 @@ + + + + + + YBlog - DRM are EVIL + + + + + + + + + + + + + +
+ + +
+

DRM are EVIL

+
+
+
+
+

DRM are EVIL (+1)

+

My wife bought about 500€ (at least) of TV Shows on iTunes. She bought the first season of Battlestar Gallactica in english (she notified the language after the dowload). DRM make it impossible to play it with french sub-titles.

+
+

+WTF? +

+
+ +

Result, my wife would never buy any TV show on iTunes. She don’t like DVD because it is not as easy to buy and to use than to simply download episodes.

+
+ +

Therefore far less money for you EVIL Copyrighter!!!!!

+
+ +

My wife won’t see these episodes.
This is a ‘LOSE-LOSE’ cooperation.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-08-15 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/05_git_create_remote_branch/code/git-create-new-branch.sh b/Scratch/en/blog/05_git_create_remote_branch/code/git-create-new-branch.sh new file mode 100644 index 0000000..6b36930 --- /dev/null +++ b/Scratch/en/blog/05_git_create_remote_branch/code/git-create-new-branch.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env zsh + +if (($#<1)); then + print -- "usage: $0:t branch_name" >&2 + exit 1 +fi + +branch=$1 +git br ${branch} +git co ${branch} +git config branch.${branch}.remote origin +git config branch.${branch}.merge refs/heads/${branch} + \ No newline at end of file diff --git a/Scratch/en/blog/05_git_create_remote_branch/index.html b/Scratch/en/blog/05_git_create_remote_branch/index.html new file mode 100644 index 0000000..39fc95c --- /dev/null +++ b/Scratch/en/blog/05_git_create_remote_branch/index.html @@ -0,0 +1,134 @@ + + + + + + YBlog - Git remote branch creation + + + + + + + + + + + + + +
+ + +
+

Git remote branch creation

+
+
+
+
+

easiest remote Git branch creation

+

I use git simply to synchronize stuff for personnal projects. Therefore, when I create a local branch I want most of time this branch to be created remotely.

+

Here is the script I use to achieve that:

+
+ +

#!/usr/bin/env zsh

+

if (($#<1)); then print – “usage: $0:t branch_name” >&2 exit 1 fi

+branch=1gitbr{branch} git co branchgitconfigbranch. {branch}.remote origin git config branch.branch. mergerefs / heads / {branch}
+
+ +

Of course, I suppose origin is already configured.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-08-17 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/06_How_I_use_git/code/git-create-new-branch b/Scratch/en/blog/06_How_I_use_git/code/git-create-new-branch new file mode 100644 index 0000000..4ce31d7 --- /dev/null +++ b/Scratch/en/blog/06_How_I_use_git/code/git-create-new-branch @@ -0,0 +1,12 @@ +#!/usr/bin/env zsh + +if (($#<1)); then + print -- "usage: $0:t branch_name" >&2 + exit 1 +fi + +branch=$1 +git br ${branch} +git co ${branch} +git config branch.${branch}.remote origin +git config branch.${branch}.merge refs/heads/${branch} diff --git a/Scratch/en/blog/06_How_I_use_git/code/git-get-remote-branches b/Scratch/en/blog/06_How_I_use_git/code/git-get-remote-branches new file mode 100644 index 0000000..dd2b3eb --- /dev/null +++ b/Scratch/en/blog/06_How_I_use_git/code/git-get-remote-branches @@ -0,0 +1,13 @@ +#!/usr/bin/env zsh + +# recup branches not on local +localbranches=( $(git br | sed 's/\*/ /') ) +remoteMissingBranches=( $(git br -r | \ + egrep -v "origin/HEAD|(${(j:|:)localbranches})" ) ) +for br in $remoteMissingBranches; do + branch=${br#origin/} + print "get remote branch $branch" + git br ${branch} + git config branch.${branch}.remote origin + git config branch.${branch}.merge refs/heads/${branch} +done diff --git a/Scratch/en/blog/06_How_I_use_git/index.html b/Scratch/en/blog/06_How_I_use_git/index.html new file mode 100644 index 0000000..d6d4859 --- /dev/null +++ b/Scratch/en/blog/06_How_I_use_git/index.html @@ -0,0 +1,243 @@ + + + + + + YBlog - Git for self + + + + + + + + + + + + + +
+ + +
+

Git for self

+
+
+
+
+

central architecture

+

I use Git to manage my personnal projects. I have a centralized repository which all my computer should synchronize with. Unfortunately I didn’t find clearly what I needed on the official Git documentation.

+

In two words, if you want to use an SVN workflow with Git (and all its advantages) here is how to proceed.

+
+

Initialisation

+

Suppose I’ve got a directory on my local computer containing a project I want to manage via Git. Here what to do:

+
+
cd to/project/directory/
+git init
+git add
+git commit
+
+ +

Now all files in the to/project/directory/ are versionned. If you want not to follow some just edit the file .gitignore

+

for example mine is:

+
+
*.swp
+.DS_Store
+ikog.py.bak
+output/Scratch/assets
+output/Scratch/en
+output/Scratch/fr
+output/Scratch/multi
+
+ +

Next, you want to put your project on a directory accessible from the web:

+
+
git clone --bare . /path/to/repository
+
+ +

Now on any computer you can do:

+
+
git clone protocol://path/to/repository local_directory
+
+ +

and local_directory will contain an up-to-date project.

+
+

You should make this operation also on the computer used to create the repository. Just to verify all will be okay.

+ +
+ +
+

The workflow

+

To resume you now have one repository on the Internet, and one or many computer associated with it. Now, what you want is to synchronize everything.

+

Before begining your work, the first thing to do is to get all modification from the Internet to your local host:

+
+
git pull
+
+ +

After that you can do (many times):

+
+
hack, hack, hack...
+git add some files
+git commit
+
+ +

When you want your local modification to be on the Internet just do a simple:

+
+
git push
+
+ +

All should be ok.

+

If you have some trouble with the push and pull verify your .git/config file ; it should contain the following lines:

+
+
...
+[remote "origin"]
+	url = protocol://url/of/the/repository
+	fetch = +refs/heads/*:refs/remotes/origin/*
+[branch "master"]
+	remote = origin
+	merge = refs/heads/master
+...
+
+ +

Branches Synchronisation

+

Well, now, all seems ok, but you have to worry about two little things. Git is all about decentralisation and branches. It is very easy to manage one branch, or many branches on the same host. But synchronize branches on many hosts is not a natural operation.

+

This is why I created two simple scripts to automate this. One for creating a branch locally and remotely. And one to get remotely created branched on your local host.

+

Then when you want to create a new branch (locally and remotely) ; you simply have to do a:

+
+git-create-new-branch branch_name +
+ +

and when you are on another computer and want to get locally all the remote branches you execute:

+
+git-get-remote-branches +
+ +

Here are the code of theese two scripts:

+
+
#!/usr/bin/env zsh
+
+if (($#<1)); then
+    print -- "usage: $0:t branch_name" >&2
+    exit 1
+fi
+
+branch=$1
+git br ${branch}
+git co ${branch}
+git config branch.${branch}.remote origin
+git config branch.${branch}.merge refs/heads/${branch}
+
+ +
+
#!/usr/bin/env zsh
+
+# recup branches not on local
+localbranches=( $(git br | sed 's/\*/ /') )
+remoteMissingBranches=( $(git br -r | \
+    egrep -v "origin/HEAD|(${(j:|:)localbranches})" ) )
+for br in $remoteMissingBranches; do
+  branch=${br#origin/}
+  print "get remote branch $branch"
+  git br ${branch}
+  git config branch.${branch}.remote origin
+  git config branch.${branch}.merge refs/heads/${branch}
+done
+
+ + +
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-08-18 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/07_Screensaver_compilation_option_for_Snow_Leopard/index.html b/Scratch/en/blog/07_Screensaver_compilation_option_for_Snow_Leopard/index.html new file mode 100644 index 0000000..130bb60 --- /dev/null +++ b/Scratch/en/blog/07_Screensaver_compilation_option_for_Snow_Leopard/index.html @@ -0,0 +1,129 @@ + + + + + + YBlog - Screensaver compilation option for Snow Leopard<sub>©</sub> + + + + + + + + + + + + + +
+ + +
+

Screensaver compilation option for Snow Leopard©

+
+
+
+
+

How to recompile your screensaver to be Snow Leopard(c) compatible

+

I upgraded to Mac OS X 10.6 Snow Leopard(c), and my YClock screensaver didn’t work on it. After searching on google, the problem seems to be just a recompilation away. Unfortunately, even recompiling it in 64 bit it didn’t work either. After a bit more research (thanks to ElectricSheep ).

+

I discovered the good parameters for compilation.

+

XCode configuration

+

For now I didn’t compiled it to work also on Tiger and Leopard. I don’t know XCode enought to know how to make the Garbage collector to be disabled on 32 bits version and enabled on 64 bits version.

+

It was a bit difficult to discover these informations. Hope this post helped someone.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-09-06 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/code/ssh-443.plist b/Scratch/en/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/code/ssh-443.plist new file mode 100644 index 0000000..5551862 --- /dev/null +++ b/Scratch/en/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/code/ssh-443.plist @@ -0,0 +1,34 @@ + + + + + Disabled + + Label + local.sshd + Program + /usr/libexec/sshd-keygen-wrapper + ProgramArguments + + /usr/sbin/sshd + -i + + Sockets + + Listeners + + SockServiceName + https + + + inetdCompatibility + + Wait + + + StandardErrorPath + /dev/null + SHAuthorizationRight + system.preferences + + diff --git a/Scratch/en/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/index.html b/Scratch/en/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/index.html new file mode 100644 index 0000000..275ff1d --- /dev/null +++ b/Scratch/en/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/index.html @@ -0,0 +1,180 @@ + + + + + + YBlog - ssh to Listen 443 on Snow Leopard + + + + + + + + + + + + + +
+ + +
+

ssh to Listen 443 on Snow Leopard

+
+
+
+
+

Surf everywhere as if you were at home

+

In order to bypass evil company firewall and to surf safely on unsafe wifi. I keep an ssh server listening on the port 443.

+

Then from my laptop or my local computer I just have to launch the marvelous

+
+
ssh -p 443 -D 9050 username@host
+
+ +

and a local socks proxy listening on port 9050 is launched. The socks proxy will transfer local requests via the ssh tunnel. Therefore I can surf locally as if I was on my own computer. I can put password and card number without fear the local wifi network to be sniffed. I simply need to configure my web browser to user the socks proxy on localhost and port 9050.

+

I get this information from this post.

+

Ssh and Snow Leopard(c)

+

Here I don’t want to talk about how great socks proxy via ssh tunneling is but how to configure my local server.

+

I have Mac with Snow Leopard(c) at home and it is far from enough to modify the /etc/sshd.config file. The system use launchd to launch starting daemons.

+

I posted the question on Apple Discussions in this discussion thread. Thanks to all guys who helped me. And the solution is:

+

Create the file /Library/LaunchDaemons/ssh-443.plist containing:

+
+
<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>Disabled</key>
+	<false/>
+	<key>Label</key>
+	<string>local.sshd</string>
+	<key>Program</key>
+	<string>/usr/libexec/sshd-keygen-wrapper</string>
+	<key>ProgramArguments</key>
+	<array>
+		<string>/usr/sbin/sshd</string>
+		<string>-i</string>
+	</array>
+	<key>Sockets</key>
+	<dict>
+		<key>Listeners</key>
+		<dict>
+			<key>SockServiceName</key>
+			<string>https</string>
+		</dict>
+	</dict>
+	<key>inetdCompatibility</key>
+	<dict>
+		<key>Wait</key>
+		<false/>
+	</dict>
+	<key>StandardErrorPath</key>
+	<string>/dev/null</string>
+        <key>SHAuthorizationRight</key>
+        <string>system.preferences</string>
+</dict>
+</plist>
+
+ +

It is a copy of /System/Library/LaunchDaemons/ssh.plist with some modifications:

+
    +
  • the SockServiceName from ssh to https.
  • +
  • the Label from com.openssh.sshd to something not existing as local.sshd
  • +
+

Tell me if it was helpfull or if you have any question.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-09-07 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/09_Why_I_didn-t_keep_whosamung-us/index.html b/Scratch/en/blog/09_Why_I_didn-t_keep_whosamung-us/index.html new file mode 100644 index 0000000..8c4345a --- /dev/null +++ b/Scratch/en/blog/09_Why_I_didn-t_keep_whosamung-us/index.html @@ -0,0 +1,134 @@ + + + + + + YBlog - Why I didn't keep whos.amung.us + + + + + + + + + + + + + +
+ + +
+

Why I didn't keep whos.amung.us

+
+
+
+
+

I changed from whos.amung.us to Google Analytics.

+

Most of time I prefer not to use the same product as everybody and try some new. But this time I believe whosamung.us had too much ads on the page. I had to put their image on my website and they only give then number of user currently on the website, not the number of visits.

+

This is why I now use google analytics. The only problem, remains for pages with no javascript support.

+

Then for now:

+
+Theorem:
+
+Google Analytics > Who’s Amung Us +
+ + +
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-09-11 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/10_Synchronize_Custom_WebSite_with_mobileMe/code/publish b/Scratch/en/blog/10_Synchronize_Custom_WebSite_with_mobileMe/code/publish new file mode 100644 index 0000000..0ea3558 --- /dev/null +++ b/Scratch/en/blog/10_Synchronize_Custom_WebSite_with_mobileMe/code/publish @@ -0,0 +1,101 @@ +#!/usr/bin/env zsh + +# Author: Yann Esposito +# Mail: yann.esposito@gmail.com +# Synchronize with "mobileMe" iDisk account. + +mobileMeUser="firstname.lastname" +siteName="siteName" + +# Depending of my hostname the +if [[ $(hostname) == 'ubuntu' ]]; then + iDisk='/mnt/iDisk' +else + iDisk="/Volumes/$mobileMeUser" +fi + +root=$HOME/Sites/$siteName +destRep=$iDisk/Web/Sites/$siteName + +[[ ! -d $root ]] && { + print -- "$root does not exist ; please verify the configuration ($0)" >&2; + exit 1 +} + +[[ ! -d $destRep ]] && { + print -- "$destRep does not exist, please mount the filesystem" >&2; + exit 1 +} + +if [[ $1 == '-h' ]]; then + print -- "usage: $0:h [-h|-a|-s]" + print -- " -a sychronize primary index" + print -- " -h show this help" + print -- " -s only swap directories" +fi + +if [[ $1 == '-a' ]]; then + print -- "Index synchronisation (${destRep:h})" + rsync -av $root/index.html ${destRep:h}/index.html +fi + +print -- "Root = $root" +print -- "Dest = $destRep" + +if [[ ! $1 = '-s' ]]; then + [[ ! -d $destRep.tmp ]] && mkdir $destRep.tmp + print -P -- "%B[Sync => tmp]%b" + result=1 + essai=1 + while (( $result > 0 )); do + rsync -arv $root/Scratch/ $destRep.tmp + result=$? + if (( $result > 0 )); then + print -P -- "%Brsync failed%b (try n°$essai)" >&2 + fi + ((essai++)) + done +fi + +# SWAP +print -P -- "%B[Directory Swap (tmp <=> target)]%b" +essai=1 +while [[ -e $destRep.old ]]; do + print -n -- "remove $destRep.old" + if ((essai>1)); then + print " (try n°$essai)" + else + print + fi + ((essai++)) + \rm -rf $destRep.old +done + +print -- " renommage du repertoire sandard vers le .old" +essai=1 +while [[ -e $destRep ]]; do + mv $destRep $destRep.old + (($?)) && print -- "Failed to rename (try n°$essai)" >&2 + ((essai++)) +done + +print -- " renaming folder tmp (new) to the standard one" +print -P -- " %BThe WebSite isn't working%b $(date)" +essai=1 +while [[ ! -e $destRep ]]; do + mv $destRep.tmp $destRep + (($?)) && print -P -- "%B[WebSite not working]%b(try n°$essai) Failed to rename (mv $destRep.tmp $destRep)" >&2 + ((essai++)) +done + +print -P -- "\t===\t%BWEBSITE SHOULD WORK NOW%b\t===" + +print -- " rename old folder to tmp folder" +essai=1 +while [[ ! -e $destRep ]]; do + mv $destRep.old $destRep.tmp + (($?)) && print -P -- "Failed to rename n°$essai" >&2 + ((essai++)) +done + +print -P -- " Publish terminated" diff --git a/Scratch/en/blog/10_Synchronize_Custom_WebSite_with_mobileMe/index.html b/Scratch/en/blog/10_Synchronize_Custom_WebSite_with_mobileMe/index.html new file mode 100644 index 0000000..b925d25 --- /dev/null +++ b/Scratch/en/blog/10_Synchronize_Custom_WebSite_with_mobileMe/index.html @@ -0,0 +1,352 @@ + + + + + + YBlog - Synchronize Custom WebSite with mobileMe + + + + + + + + + + + + + +
+ + +
+

Synchronize Custom WebSite with mobileMe

+
+
+
+
+

Update (2012/01/11)

+

iDisk should soon disapear. This entry is mainly obsolescent now.

+

Update (2009/10/28)

+

I updated my script which is now incremental. Since the writing of this article, Apple(c) had made many efforts about the bandwith of its European servers.

+
+

WebDav terror

+

I live in France and iDisk upload is just terrible. Upload speed remind me the old 56k modem. Most operations such as list the content of a directory take at least 30 seconds (for 15 elements). Renaming a directory fail most of time.

+

Apple(c) use a WebDav server to host files. It works on port 80 (like http). I realized WebDav via https work better (2 to 3 times faster with far less errors). But even https is too slow.

+

I upload from my Mac and sometimes from an Ubuntu PC (iDisk mounted with webdavfs).

+

Synchronize safely the website

+

Here is the script I use in order to synchronize my website with maximum safety. It try each operations until it works.

+

The idea are:

+
    +
  • synchronize to a temporary folder then swap the name therefore the website isn’t accessible only during the swap time. It takes only the time of two rename.
  • +
  • reiterate all operations until they work (for example, renaming).
  • +
+

For now I use rsync which in fact is no more efficient than a simple cp with WebDav. And I should use a method to keep track of elements who have changed. before the publication.

+

In fact when I’m on a Mac, I use Transmit which is very cool and far more efficient than the Finder to synchronize files. After the synchronization, I swap the directories.

+

My script take a -s option in order to make only the swap option. It also take a -a in order to put the new index.html which should point to the new homepage (not the iWeb one).

+

In order to keep this script working for you, just modify the username by yours (the value of the mobileMeUser).

+
+
#!/usr/bin/env zsh
+
+# Script synchronisant le site sur me.com
+# normalement, le site est indisponible le moins de temps possible
+# le temps de deux renommages de répertoire
+
+mobileMeUser="yann.esposito"
+siteName="siteName"
+
+# Depending of my hostname the 
+if [[ $(hostname) == 'ubuntu' ]]; then
+    iDisk='/mnt/iDisk'
+else
+    iDisk="/Volumes/$mobileMeUser"
+fi
+
+root=$HOME/Sites/$siteName
+destRep=$iDisk/Web/Sites/$siteName
+
+[[ ! -d $root ]] && { 
+    print -- "$root n'existe pas ; vérifiez la conf" >&2; 
+    exit 1 
+}
+
+[[ ! -d $destRep ]] && { 
+    print -- "$destRep n'existe pas, veuillez remonter le FS" >&2; 
+    exit 1 
+}
+
+if [[ $1 == '-h' ]]; then
+    print -- "usage: $0:h [-h|-a|-s]"
+    print -- "  -a sychronise aussi l'index"
+    print -- "  -h affiche l'aide"
+    print -- "  -s swappe simplement les répertoires"
+fi
+
+if [[ $1 == '-a' ]]; then
+    print -- "Synchronisation de l'index (${destRep:h})"
+    rsync -av $root/index.html ${destRep:h}/index.html
+fi
+
+print -- "Root = $root"
+print -- "Dest = $destRep"
+
+if [[ ! $1 = '-s' ]]; then
+    [[ ! -d $destRep.tmp ]] && mkdir $destRep.tmp
+    print -P -- "%B[Sync => tmp]%b"
+    result=1
+    essai=1
+    while (( $result > 0 )); do
+        rsync -arv $root/Scratch/ $destRep.tmp
+        result=$?
+        if (( $result > 0 )); then
+            print -P -- "%BEchec du rsync%b (essai n°$essai)" >&2
+        fi
+        ((essai++))
+    done
+fi
+
+# SWAP
+print -P -- "%B[Swap des Répertoires (tmp <=> target)]%b"
+essai=1
+while [[ -e $destRep.old ]]; do
+    print -n -- "suppression de $destRep.old"
+    if ((essai>1)); then 
+        print " (essai n°$essai)"
+    else
+        print
+    fi
+    ((essai++))
+    \rm -rf $destRep.old
+done
+
+print -- "  renommage du repertoire sandard vers le .old"
+essai=1
+while [[ -e $destRep ]]; do
+    mv $destRep $destRep.old 
+    (($?)) && print -- "Echec du renommage (essai n°$essai)" >&2
+    ((essai++))
+done
+
+print -- "  renommage du repertoire tmp (nouveau) vers le standard"
+print -P -- "  %BSite Indisponible%b $(date)"
+essai=1
+while [[ ! -e $destRep ]]; do
+    mv $destRep.tmp $destRep
+    (($?)) && print -P -- "%B[Site Indisponible]%b(essai n°$essai) Echec du renommage (mv $destRep.tmp $destRep)" >&2
+    ((essai++))
+done
+
+print -P -- "\t===\t%BSITE DISPONIBLE%b\t==="
+
+print -- "  renommage du repertoire old vers le tmp"
+essai=1
+while [[ ! -e $destRep ]]; do
+    mv $destRep.old $destRep.tmp
+    (($?)) && print -P -- "Echec du renommage n°$essai" >&2
+    ((essai++))
+done
+
+print -P -- "  publication terminée"
+
+ +
+
#!/usr/bin/env zsh
+
+# Author: Yann Esposito
+#   Mail: yann.esposito@gmail.com
+# Synchronize with "mobileMe" iDisk account.
+
+mobileMeUser="firstname.lastname"
+siteName="siteName"
+
+# Depending of my hostname the 
+if [[ $(hostname) == 'ubuntu' ]]; then
+    iDisk='/mnt/iDisk'
+else
+    iDisk="/Volumes/$mobileMeUser"
+fi
+
+root=$HOME/Sites/$siteName
+destRep=$iDisk/Web/Sites/$siteName
+
+[[ ! -d $root ]] && { 
+    print -- "$root does not exist ; please verify the configuration ($0)" >&2; 
+    exit 1 
+}
+
+[[ ! -d $destRep ]] && { 
+    print -- "$destRep does not exist, please mount the filesystem" >&2; 
+    exit 1 
+}
+
+if [[ $1 == '-h' ]]; then
+    print -- "usage: $0:h [-h|-a|-s]"
+    print -- "  -a sychronize primary index"
+    print -- "  -h show this help"
+    print -- "  -s only swap directories"
+fi
+
+if [[ $1 == '-a' ]]; then
+    print -- "Index synchronisation (${destRep:h})"
+    rsync -av $root/index.html ${destRep:h}/index.html
+fi
+
+print -- "Root = $root"
+print -- "Dest = $destRep"
+
+if [[ ! $1 = '-s' ]]; then
+    [[ ! -d $destRep.tmp ]] && mkdir $destRep.tmp
+    print -P -- "%B[Sync => tmp]%b"
+    result=1
+    essai=1
+    while (( $result > 0 )); do
+        rsync -arv $root/Scratch/ $destRep.tmp
+        result=$?
+        if (( $result > 0 )); then
+            print -P -- "%Brsync failed%b (try n°$essai)" >&2
+        fi
+        ((essai++))
+    done
+fi
+
+# SWAP
+print -P -- "%B[Directory Swap (tmp <=> target)]%b"
+essai=1
+while [[ -e $destRep.old ]]; do
+    print -n -- "remove $destRep.old"
+    if ((essai>1)); then 
+        print " (try n°$essai)"
+    else
+        print
+    fi
+    ((essai++))
+    \rm -rf $destRep.old
+done
+
+print -- "  renommage du repertoire sandard vers le .old"
+essai=1
+while [[ -e $destRep ]]; do
+    mv $destRep $destRep.old 
+    (($?)) && print -- "Failed to rename (try n°$essai)" >&2
+    ((essai++))
+done
+
+print -- "  renaming folder tmp (new) to the standard one"
+print -P -- "  %BThe WebSite isn't working%b $(date)"
+essai=1
+while [[ ! -e $destRep ]]; do
+    mv $destRep.tmp $destRep
+    (($?)) && print -P -- "%B[WebSite not working]%b(try n°$essai) Failed to rename (mv $destRep.tmp $destRep)" >&2
+    ((essai++))
+done
+
+print -P -- "\t===\t%BWEBSITE SHOULD WORK NOW%b\t==="
+
+print -- "  rename old folder to tmp folder"
+essai=1
+while [[ ! -e $destRep ]]; do
+    mv $destRep.old $destRep.tmp
+    (($?)) && print -P -- "Failed to rename n°$essai" >&2
+    ((essai++))
+done
+
+print -P -- "  Publish terminated"
+
+ + +
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-09-11 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/11_Load_Disqus_Asynchronously/index.html b/Scratch/en/blog/11_Load_Disqus_Asynchronously/index.html new file mode 100644 index 0000000..4f68622 --- /dev/null +++ b/Scratch/en/blog/11_Load_Disqus_Asynchronously/index.html @@ -0,0 +1,160 @@ + + + + + + YBlog - Load Disqus Asynchronously + + + + + + + + + + + + + +
+ + +
+

Load Disqus Asynchronously

+
+
+
+
+

Update

+

In fact this method works for old threads. But it fails to create new post threads. This is why I tried and be conquered by intensedebate, as you can see in the bottom of this page.

+

Remark I didn’t have any comment on my blog when I switched. Therefore my lack of influence was a good thing :-).

+
+

Before begining, I must state that I love Disqus.

+

I know there is a similar blog entry at Trephine.org. Here I just add a straight and easy way to load disqus asynchronously using jQuery.

+

I also know there is a jQuery plugin to make just that. Unfortunately I had some issue with CSS.

+

Now let’s begin.

+
+

Why?

+

Why should I want to load the disqus javascript asynchronously?

+
    +
  • Efficiency: I don’t want my page to wait the complete execution of disqus script to load.
  • +
  • More independance: when disqus is down, my page is blocked!
  • +
+
+

How?

+

I give a solution with jQuery, but I’m certain it will work with many other js library.

+

Javascript

+

replace:

+
+
<script type="text/javascript" src="http://disqus.com/forums/YOUR_DISQUS_ID/embed.js"></script>
+
+ +

by

+
+
window.disqus_no_style=true;
+$(document).ready(function(){
+    $.getScript("http://disqus.com/forums/YOUR_DISQUS_ID/embed.js");
+});
+
+ +

If you forget the window.disqus_no_style=true; then your page will be blank. Simply because without this option, the javascript use a document.write action after the document was closed, which cause a complete erasing of it.

+

CSS

+

But with this option you still need to provide a CSS. This is why you have to copy the css code from the embed.js file and rewrite it in a CSS file. You can download the CSS I obtained.

+
+

Now it’s done. I believe all should be fine but I just finished the manip for my own site only 1 hour ago. Therefore there should be some error, tell me if it is the case.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-09-17 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/index.html b/Scratch/en/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/index.html new file mode 100644 index 0000000..5478209 --- /dev/null +++ b/Scratch/en/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/index.html @@ -0,0 +1,147 @@ + + + + + + YBlog - Disqus versus Intense Debate (Why I switched) + + + + + + + + + + + + + +
+ + +
+

Disqus versus Intense Debate (Why I switched)

+
+
+
+
+

Disqus vs. Intense Debate

+

I made a blog entry about how I tried to integrate Disqus. I had to wait Disqus comment to be displayed before loading correctly my page. This is why I tried to include it in a “non-blocking” way. Unfortunately, I had difficulties to make it works correctly.

+

Furthermore, it was not trivial to make comment to be shared between multiple version of the same page (each page has three differents representations, one for each language and one more for the multi-language version).

+

I am a bit sad to quit Disqus because I must confess giannii had helped me has efficiently as he could. But the problem I had with disqus are inherent to some design choice not simply technical ones.

+

During the time I tried to integrate Disqus I never tried Intense Debate. Now that I have tried, i must confess it does exactly what I needed.

+

In order to make it fully asynchronous, you’ve just to download their common js and replace the following line:

+
+
document.getElementsByTagName("head")[0].appendChild(commentScript);
+
+ +

by:

+
+
$(document).ready( function() {
+    document.getElementsByTagName("head")[0].appendChild(commentScript);
+});
+
+ +

And the Winner is: Intense Debate

+

To conclude, main advantages (for me) of Intense Debate over Disqus:

+
    +
  • Load Asynchronously ; don’t block my website
  • +
  • Add for free buttons like “share to any” and load them asynchronously.
  • +
+

Voilà.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-09-28 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2009-09-jQuery-Tag-Cloud/index.html b/Scratch/en/blog/2009-09-jQuery-Tag-Cloud/index.html new file mode 100644 index 0000000..2bb5aa8 --- /dev/null +++ b/Scratch/en/blog/2009-09-jQuery-Tag-Cloud/index.html @@ -0,0 +1,316 @@ + + + + + + YBlog - jQuery Tag Cloud + + + + + + + + + + + + + +
+ + +
+

jQuery Tag Cloud

+
+
+
+
+

Here is how I done the tag cloud of my blog. It is done mostly in jQuery. All my site is static and pages are generated with nanoc. It is (in my humble opinion) the modern geek way to make a website. The tagcloud should work for machine with and without javascript.

+

This is why I’ll give only a Ruby Generator, not a full javascript generator. But you can easily translate from Ruby to Javascript.

+

Here is what you should obtain:

+
+
+<%= tagCloud %> +
+ +
+

jQuery

+

Here is the simple jQuery code:

+
+
    $(document).ready( function(){$('.list').hide();} );
+    function tagSelected(id) {
+        $('.list').hide();
+        $('#'+id).fadeIn();
+        $('.tag.selected').removeClass('selected');
+        $('#tag_'+id).addClass('selected');
+    }
+
+ +

This code will hide all the div containing links to articles containing the tag. And create a function do show the div containing the tag.

+

For each tag I create a span element:

+
+
    <span   style="font-size: 1.0em;" 
+            class="tag" 
+            onClick="tagSelected('[TAG]')" 
+            id="tag_[TAG]">
+        [TAG]
+    </span> 
+
+ +

and a div containing links associtated to this tag:

+
+
    <div id="[TAG]">
+        <h4>[TAG]</h4>
+        <ul>
+            <li> LINK 1 </li>
+            <li> LINK 2 </li>
+        </ul>
+    </div> 
+
+ +
+

nanoc

+

Here is how I generate this using nanoc 2.

+

If you want to make it fully jQuery one, it shouldn’t be too difficult, to use my ruby code and translate it into javascript.

+

In a first time tags correpond of the list of all tags.

+
+
def tags
+    return @items.tags.join(', ')
+end
+
+ +

A function to create a data structure associating to each tag its occurence.

+
+
# generate an hash tag => number of occurence of tag
+def tagNumber
+    tags={}
+    @items.each do |p|
+        if p.tags.nil?
+            next
+        end
+        p.tags.each do |t|
+            if tags[t]
+                tags[t]+=1
+            else
+                tags[t]=1
+            end
+        end
+    end
+    return tags
+end
+
+ +

I also need a data structure who associate to each tag a list of pages (at least url and title).

+
+
# generate an hash tag => [ page1, page2 ... ]
+def tagRefs
+    tagLinks={}
+    @items.each do |p|
+        if p.tags.nil?
+            next
+        end
+        p.tags.each do |t|
+            if tagLinks[t].nil?
+                tagLinks[t]=[ p ]
+            else
+                tagLinks[t] <<= p
+            end
+        end
+    end
+    return tagLinks
+end
+
+ +

Calculate the real size of each tag to be displayed.

+

I choosen not to use the full range of size for all the tag. Because if no tag has more than n (here 10) occurences, then it doesn’t deserve to be of the maximal size.

+
+
def tagRealSize
+    tags=tagNumber
+    max=tags.values.max
+    min=tags.values.min
+    # size in CSS em.
+    minSize=1.0
+    maxSize=2.5
+    tagSize={}
+    tags.each do |t,n|
+        if ( max == min )
+            tagSize[t]=minSize
+        else
+            # normalized value between 0 and 1
+            # if not tag appear more than 10 times, 
+            # then it cannot have the maximal size
+            tagSize[t]=[ ( n - min + 0.0 ) / ( max - min ) , 
+                         (n - min) / 10.0 ].min
+            # from normalized size to real size
+            tagSize[t]=( tagSize[t] ) * (maxSize - minSize) + minSize
+        end
+    end
+    return tagSize
+end
+
+ +

Finaly a function to generate the XHTML/jQuery code

+
+
# generate an XHTML/jQuery code for tag cloud
+def tagCloud
+    tagLinks=tagRefs
+    tagSize=tagRealSize
+
+    # begin to write the code
+    tagCloud=%{<script type="text/javascript">
+        $(document).ready( function(){$('.list').hide();} );
+        function tagSelected(id) {
+            $('.list').hide();
+            $('#'+id).fadeIn();
+            $('.tag.selected').removeClass('selected');
+            $('#tag_'+id).addClass('selected');
+        }
+    </script><div id="tagcloud">}
+    # Creation of the tags <span>
+    tagSize.sort{|a,b| a[0].downcase <=> b[0].downcase}.each do |t,s|
+        tag_in_id=t.gsub(/\W/,'_')
+        # HTML protected version of the tag
+        # for example, replace ' ' by '&nbsp;'
+        protected=t.gsub(/&/,'&amp;').gsub(/ /,'&nbsp;').gsub(/</,'&lt;').gsub(/>/,'&gt;')
+        tagCloud <<= %{
+            <span style="font-size: #{s}em;" 
+                  class="tag" 
+                  onClick="tagSelected('#{tag_in_id}')" 
+                  id="tag_#{tag_in_id}">
+                #{protected}
+            </span> }
+    end
+    tagCloud <<= %{</div><div id="hiddenDivs" >}
+    # Creation of the divs containing links associated to a tag.
+    tagLinks.each do |t,l|
+        tag_in_id=t.gsub(/\W/,'_')
+        tagCloud <<= %{
+            <div id="#{tag_in_id}" class="list">
+                <h4>#{t}</h4><ul>}
+        # generate the link list
+        l.each do |p|
+            tagCloud <<= %{<li><a href="#{p.path}">#{p.title}</a></li>}
+        end
+        tagCloud <<= %{</ul></div>}
+    end
+    tagCloud <<= %{</div>}
+    return tagCloud # yeah I know it is not necessary
+end
+
+ +

You can download the complete file to put in your ‘lib’ directory.

+

Of course to be nice you need the associated CSS

+
+

+// Change the color when mouse over
+.tag:hover {
+  color: #cc0000; }
+
+// Change the color when tag selected
+.tag.selected {
+  color: #6c0000; }
+
+// a bit of space and pointer cursor
+.tag {
+  cursor: pointer;
+  margin-left: .5em;
+  margin-right: .5em; }
+
+ +

That’s all folks.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-09-23 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2009-09-replace-all-except-some-part/index.html b/Scratch/en/blog/2009-09-replace-all-except-some-part/index.html new file mode 100644 index 0000000..6dc13cb --- /dev/null +++ b/Scratch/en/blog/2009-09-replace-all-except-some-part/index.html @@ -0,0 +1,191 @@ + + + + + + YBlog - replace all except some part + + + + + + + + + + + + + +
+ + +
+

replace all except some part

+
+
+
+
+

My problem is simple:

+

I want to filter a text except some part of it. I can match easily the part I don’t want to be filtered. For example

+
+
...
+text
+...
+BEGIN not to filter
+...
+text
+...
+END not to filter
+...
+text
+...
+
+ +

I searched a better way to do that, but the best I can do is using split and scan.

+
+
def allExceptCode( f, content )
+    # Beware the behaviour will change if you add
+    # parenthesis (groups) to the regexp!
+    regexp=/<code[^>]*>.*?<\/code>|<pre[^>]*>.*?<\/pre>/m
+    tmp=""
+    mem=[]
+    content.scan(regexp).each do |c|
+        mem <<= c
+    end
+    i=0
+    content.split(regexp).each do |x|
+        tmp <<= send(f,x) 
+        if not mem[i].nil? 
+            tmp <<= mem[i]
+            i+=1
+        end
+    end
+    tmp
+end
+
+ +

An usage is:

+
+
def filter(content)
+    content.gsub(/e/,'X')
+end
+...
+allExceptCode(:filter, content)
+...
+
+ +

A better syntax would be:

+
+
# !!!!!!!!!! THIS SYNTAX DOES NOT WORK !!!!!!! #
+def allExceptCode( f, content )
+    regexp=/<code[^>]*>.*?<\/code>/m
+    tmp=""
+    content.split(regexp).each do |x|
+        separator=$&
+        tmp <<= send(f,x) 
+        if not separator.nil?
+            tmp <<= separator
+        end
+    end
+    tmp
+end
+
+ +

I would expect the split make a search on a regular expression and then give the matched expression into the $& variable. But it is not the case.

+

If someone know a nicer way to do that I will be happy to know how.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-09-22 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/code/publish b/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/code/publish new file mode 100644 index 0000000..d028472 --- /dev/null +++ b/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/code/publish @@ -0,0 +1,118 @@ +#!/usr/bin/env zsh + +# Script synchronisant le site sur me.com +# normalement, le site est indisponible le moins de temps possible +# le temps de deux renommages de répertoire + +# get configuration +# mostly directories +source $0:h/config + +# get trycp function (copy until success) +source $0:h/webdav-framework + +if [[ $1 == '-h' ]]; then + print -- "usage : $0:h [-h|-s|-d]" + print -- " -a sychronise aussi l'index" + print -- " -h affiche l'aide" + print -- " -d modification directe (pas de swap)" + print -- " -s swappe simplement les répertoires" +fi + +# publication incrementale +function incrementalPublish { + local ydestRep=$destRep$suffix + localRef="$srcRep/map.yrf" + print -- "Creation du fichier de references" + create-reference-file.sh > $localRef + remoteRef="/tmp/remoteSiteMapRef.$$.yrf" + if [[ ! -e "$ydestRep/map.yrf" ]]; then + # pas de fichier de reference sur la cible + print -- "pas de fichier de reference sur la cible, passage en mode rsync" + rsyncPublish + swap + else + trycp "$ydestRep/map.yrf" "$remoteRef" + typeset -U filesToUpdate + filesToUpdate=( $(diff $localRef $remoteRef | awk '/^[<>]/ {print $2}' ) ) + if ((${#filesToUpdate} == 1)); then + print -- "Seul le fichier ${filesToUpdate} sera téléversé" + elif ((${#filesToUpdate}<10)); then + print -- "${#filesToUpdate} fichiers seront téléversés :" + print -- "${filesToUpdate}" + else + print -- "${#filesToUpdate} fichiers seront téléversés" + fi + # copy all file with some differences + # except the map in case of error + for element in $filesToUpdate; do + if [[ $element == "/map.yrf" ]]; then + continue + fi + if [[ -e $srcRep$element ]]; then + trycp $srcRep$element $ydestRep$element + else + tryrm $ydestRep$element + fi + done + # if all went fine, copy the map file + trycp $srcRep/map.yrf $ydestRep/map.yrf + # remove the temporary file + \rm $remoteRef + # if we have used the tmp directory we swap + if [[ "$suffix" != "" ]]; then + swap + fi + fi +} + +# publication via rsync +function rsyncPublish { + result=1 + essai=1 + while (( $result > 0 )); do + print -- rsync -arv $srcRep/ $destRep.tmp + if ((!testmode)); then + rsync -arv $srcRep/ $destRep.tmp + fi + result=$? + if (( $result > 0 )); then + print -P -- "%BEchec du rsync%b (essai n°$essai)" >&2 + fi + ((essai++)) + done +} + +# swap +function swap { + print -P -- "%B[Directory Swap (tmp <=> target)]%b" + [[ -e $destRep.old ]] && tryrm $destRep.old + + print -- " renommage du repertoire sandard vers le .old" + tryrename $destRep $destRep.old + + print -- " renommage du repertoire tmp (nouveau) vers le standard" + print -P -- "%B[Site Indisponible]%b $(date)" + tryrename $destRep.tmp $destRep + print -P -- "%B[Site Disponible]%b $(date)" + + print -- " renommage du repertoire old vers le tmp" + tryrename $destRep.old $destRep.tmp + + print -P -- " publication terminée" +} + +print -- "Root = $webroot" +print -- "Dest = $destRep" + +if [[ "$1" = "-s" ]]; then + swap +else + if [[ "$1" = "-d" ]]; then + suffix="" + else + suffix=".tmp" + fi + print -P -- "%BSync%b[${Root:t} => ${destRep:t}$suffix]" + incrementalPublish +fi diff --git a/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/code/webdav-framework b/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/code/webdav-framework new file mode 100644 index 0000000..4ec7888 --- /dev/null +++ b/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/code/webdav-framework @@ -0,0 +1,108 @@ +#!/usr/bin/env zsh + +function samelineprint { + print -n -P -- "\r$*" +} + +# avec 1 essai par seconde: 300 = 5 minutes +maxessais=300 + +# try to create a directory until success +function trymkdir { + target="$1" + print -- mkdir -p $target + local essai=1 + while ! mkdir -p $target; do + samelineprint "Echec: essai n°$essai" + ((essai++)) + ((essai>maxessais)) && exit 5 + done + print +} + +# try to copy until success +function trycp { + element="$1" + target="$2" + if [[ ! -d ${target:h} ]]; then + trymkdir ${target:h} + fi + local essai=1 + print -- cp $element $target + while ! \cp $element $target; do + samelineprint "Echec: essai n°$essai" + ((essai++)) + ((essai>maxessais)) && exit 5 + done + print +} + +# try to remove until success +function tryrm { + target="$1" + local essai=1 + local options='' + [[ -d $target ]] && options='-rf' + print -- rm $options $target + while ! rm $options $target; do + samelineprint "Echec: essai n°$essai" + ((essai++)) + ((essai>maxessais)) && exit 5 + done + essai=1 + while [[ -e $element ]]; do + samelineprint "rm reussi mais fichier source non disparu n°$essai" + sleep 1 + ((essai++)) + ((essai>maxessais)) && exit 5 + done + print +} + +# try to rename until success +function tryrename { + element="$1" + target="$2" + local essai=1 + while [[ -e $target ]]; do + samelineprint "Echec n°$essai le fichier $target existe déjà" + ((essai++)) + ((essai>maxessais)) && exit 5 + sleep 1 + done + print -- mv $element $target + while ! mv $element $target; do + samelineprint "Echec: essai n°$essai" + ((essai++)) + ((essai>maxessais)) && exit 4 + done + essai=1 + while [[ -e $element ]]; do + samelineprint "mv reussi mais fichier source non disparu n°$essai" + sleep 1 + ((essai++)) + ((essai>maxessais)) && exit 5 + done + print +} + +# try to move until success +function trymv { + element="$1" + target="$2" + local essai=1 + print -- mv $element $target + while ! mv $element $target; do + samelineprint "Echec: essai n°$essai" + ((essai++)) + ((essai>maxessais)) && exit 5 + done + essai=1 + while [[ -e $element ]]; do + samelineprint "mv reussi mais fichier source non disparu n°$essai" + sleep 1 + ((essai++)) + ((essai>maxessais)) && exit 5 + done + print +} diff --git a/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/index.html b/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/index.html new file mode 100644 index 0000000..9f47fbe --- /dev/null +++ b/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/index.html @@ -0,0 +1,196 @@ + + + + + + YBlog - custom website synchronisation with mobileme (2) + + + + + + + + + + + + + +
+ + +
+

custom website synchronisation with mobileme (2)

+
+
+
+
+

I already talked about how I synchronized my website with mobileme. I ameliorated this script in order to make it incremental.

+

Here is my new script, it first create a map which associate to each file its hash. After that it compare this file to the remote one. Then for each different file, update the content.

+

Even with this script I also have some problem. Mostly due to ‘webdav’ issues. For example, renaming a folder work really badly (on Linux at least). I use webdavfs. For example:

+
+ mv folder folder2 +
+ +

It returns OK and I’ve got:

+
+ $ ls folder folder2 +
+ +

Booh….

+

In order to handle most webdav issues I use a framework in zsh. It handle almost all except the correct renaming of folder. Working on it… Anyway here is the code I use.

+
+

#!/usr/bin/env zsh

+

function samelineprint { print -n -P – “$*” }

+

avec 1 essai par seconde: 300 = 5 minutes

+

maxessais=300

+

try to create a directory until success

+

function trymkdir { target=“$1" print -- mkdir -p $target local essai=1 while ! mkdir -p $target; do samelineprint "Echec: essai n°$essai” ((essai++)) ((essai>maxessais)) && exit 5 done print }

+

try to copy until success

+

function trycp { element=“$1” target=“$2" if [[ ! -d ${target:h} ]]; then trymkdir target: hfilocalessai = 1print −  − cpelement $target while ! \cp $element $target; do samelineprint "Echec: essai n°$essai” ((essai++)) ((essai>maxessais)) && exit 5 done print }

+

try to remove until success

+

function tryrm { target=“$1" local essai=1 local options='' [[ -d $target ]] && options=‘-rf’ print – rm optionstarget while ! rm optionstarget; do samelineprint”Echec: essai n°$essai" ((essai++)) ((essai>maxessais)) && exit 5 done essai=1 while [[ -e $element ]]; do samelineprint “rm reussi mais fichier source non disparu n°$essai” sleep 1 ((essai++)) ((essai>maxessais)) && exit 5 done print }

+

try to rename until success

+

function tryrename { element=“$1” target=“$2" local essai=1 while [[ -e $target ]]; do samelineprint”Echec n°essailefichiertarget existe déjà" ((essai++)) ((essai>maxessais)) && exit 5 sleep 1 done print – mv elementtarget while ! mv elementtarget; do samelineprint “Echec: essai n°$essai" ((essai++)) ((essai>maxessais)) && exit 4 done essai=1 while [[ -e $element ]]; do samelineprint”mv reussi mais fichier source non disparu n°$essai" sleep 1 ((essai++)) ((essai>maxessais)) && exit 5 done print }

+

try to move until success

+function trymv { element=“$1” target=“$2" local essai=1 print -- mv $element targetwhile!mvelement $target; do samelineprint "Echec: essai n°$essai” ((essai++)) ((essai>maxessais)) && exit 5 done essai=1 while [[ -e $element ]]; do samelineprint “mv reussi mais fichier source non disparu n°$essai” sleep 1 ((essai++)) ((essai>maxessais)) && exit 5 done print }
+
+ +

And here is the code on how I synchronize my website. There is a little cryptic code. It correspond a problem caused by the bluecloth filter which is a markdown program made in ruby. Each time my email is written it is transformed differently. This is why I remove this part from the content of each html file. Without it, all my files containing email are different at each regeneration of my website.

+
+

#!/usr/bin/env zsh

+

Script synchronisant le site sur me.com

+

normalement, le site est indisponible le moins de temps possible

+

le temps de deux renommages de répertoire

+

get configuration

+

mostly directories

+

source $0:h/config

+

get trycp function (copy until success)

+

source $0:h/webdav-framework

+

if [[ $1 == ‘-h’ ]]; then print – “usage : $0:h [-h|-s|-d]” print – " -a sychronise aussi l’index" print – " -h affiche l’aide" print – " -d modification directe (pas de swap)" print – " -s swappe simplement les répertoires" fi

+

publication incrementale

+

function incrementalPublish { local ydestRep=destRepsuffix localRef=“$srcRep/map.yrf" print -- "Creation du fichier de references" create-reference-file.sh > $localRef remoteRef=”/tmp/remoteSiteMapRef.$$.yrf" if [[ ! -e "$ydestRep/map.yrf" ]]; then # pas de fichier de reference sur la cible print – “pas de fichier de reference sur la cible, passage en mode rsync” rsyncPublish swap else trycp “$ydestRep/map.yrf" "$remoteRef” typeset -U filesToUpdate filesToUpdate=( (difflocalRef $remoteRef | awk ’/1/ {print $2}' ) ) if ((${#filesToUpdate} == 1)); then print – “Seul le fichier ${filesToUpdate} sera téléversé" elif ((${#filesToUpdate}<10)); then print –”${#filesToUpdate} fichiers seront téléversés :" print -- "${filesToUpdate}" else print – "${#filesToUpdate} fichiers seront téléversés" fi # copy all file with some differences # except the map in case of error for element in $filesToUpdate; do if [[ $element == “/map.yrf” ]]; then continue fi if [[ -e srcRepelement ]]; then trycp srcRepelement ydestRepelement else tryrm ydestRepelement fi done # if all went fine, copy the map file trycp srcRep / map. yrfydestRep/map.yrf # remove the temporary file }

+

publication via rsync

+

function rsyncPublish { result=1 essai=1 while (( result > 0)); doprint −  − rsync − arvsrcRep/ destRep. tmpif((!testmode)); thenrsync − arvsrcRep/ destRep. tmpfiresult = ? if (( $result > 0 )); then print -P -- "%BEchec du rsync%b (essai n°$essai)" >&2 fi ((essai++)) done }

+

swap

+

function swap { print -P – “%B[Directory Swap (tmp <=> target)]%b” [[ -e $destRep.old ]] && tryrm $destRep.old

+
print -- "  renommage du repertoire sandard vers le .old"
+tryrename $destRep $destRep.old 
+
+print -- "  renommage du repertoire tmp (nouveau) vers le standard"
+print -P -- "%B[Site Indisponible]%b $(date)"
+tryrename $destRep.tmp $destRep
+print -P -- "%B[Site Disponible]%b $(date)"
+
+print -- "  renommage du repertoire old vers le tmp"
+tryrename $destRep.old $destRep.tmp
+
+print -P -- "  publication terminée"
+

}

+

print – “Root = $webroot" print -- "Dest = $destRep”

+if [[ “$1” = “-s” ]]; then swap else if [[ “$1” = “-d” ]]; then suffix=“" else suffix=”.tmp" fi print -P – “%BSync%b[Root: t =  > {destRep:t}$suffix]” incrementalPublish fi
+
+ +

This is my way to replace rsync with filesystem not handling it. Hope it is usefull. I’ll be happy to hear a way to handle the webdav rename folder problem. This is really annoying.

+
+
+
    +
  1. <>

  2. +
+
+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-10-28 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2009-10-30-How-to-handle-evil-IE/code/ie.js b/Scratch/en/blog/2009-10-30-How-to-handle-evil-IE/code/ie.js new file mode 100644 index 0000000..6b047db --- /dev/null +++ b/Scratch/en/blog/2009-10-30-How-to-handle-evil-IE/code/ie.js @@ -0,0 +1,17 @@ +// Remove all CSS I don't want to use on IE +$('link[rel=stylesheet]').each(function(i) +{ + if (this.getAttribute('href') == '/css/layout.css') + this.disabled = true; + if (this.getAttribute('href') == '/css/shadows.css') + this.disabled = true; + if (this.getAttribute('href') == '/css/gen.css') + this.disabled = true; +}) ; + +// Append the CSS for IE only +$('head').append(''); + +// I also add a message on top of the page +$('body').prepend('

Avec Firefox et Safari cette page est bien plus jolie !This page is far nicer with Firefox and Safari!

.
'); + diff --git a/Scratch/en/blog/2009-10-30-How-to-handle-evil-IE/index.html b/Scratch/en/blog/2009-10-30-How-to-handle-evil-IE/index.html new file mode 100644 index 0000000..fbd552a --- /dev/null +++ b/Scratch/en/blog/2009-10-30-How-to-handle-evil-IE/index.html @@ -0,0 +1,144 @@ + + + + + + YBlog - How to handle evil IE + + + + + + + + + + + + + +
+ + +
+

How to handle evil IE

+
+
+
+
+

For developer IE is a nightmare. This is why, I use a method to disable my standard CSS and enable a IE only CSS. I use jQuery to accomplish that.

+
+ $(document).ready( function() { if ($.browser[“msie”]) { // include the ie.js file $(‘head’).append(’ + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-10-30 +
+ +
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2009-10-Focus-vs-Minimalism/index.html b/Scratch/en/blog/2009-10-Focus-vs-Minimalism/index.html new file mode 100644 index 0000000..56fe511 --- /dev/null +++ b/Scratch/en/blog/2009-10-Focus-vs-Minimalism/index.html @@ -0,0 +1,163 @@ + + + + + + YBlog - Focus > Minimalism + + + + + + + + + + + + + +
+ + +
+

Focus > Minimalism

+
+
+
+
+

I believe the goal researched by minimalism is Focus. But I don’t believe minimalism should be the goal. Focus should be the goal, and I believe minimalism isn’t necessary to reach it.

+

This is why my design is not minimalist, but I decided to remove most of the navigation stuff of all pages of my website. May be I’ll prefer to hide the menu only when you are on blog article. For now, I hide the menu everywhere on the website.

+
+

technical details

+

For those who want the technical details behind the show/hide menu, here is the simple jQuery code.

+

The HTML:

+
+
<div id="menuButton"></div>
+<div id="entete">#content of the menu</div>
+
+ +

The CSS:

+
+

#menuButton { font-size: 2em; height: 2em; line-height: 1.8em; width: 2em; position: fixed; left: 0; top: 0; z-index: 9001 }

+

menuButton:hover {

+

cursor: pointer; }

+

entete {

+top: 5em; left: 0; position: fixed; width: 10em; z-index: 9000; } ~~~~~~ +
+ +

The javascript code (using jQuery)

+
+
function hideMenu() {
+    $('#entete').animate({left:"-10em"}, 500 );
+    $('#menuButton').html('&rarr;');
+}
+function showMenu() {
+    $('#entete').animate({left:"0em"}, 500 );
+    $('#menuButton').html('&larr;');
+}
+function toggleMenu() {
+    if ( $('#entete').css('left')=='-10em' ) {
+        showMenu();
+    } else {
+        hideMenu();
+    }
+}
+
+ +

And the result is shown in the top left corner of this website.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-10-22 +
+ +
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2009-10-How-to-preload-your-site-with-style/index.html b/Scratch/en/blog/2009-10-How-to-preload-your-site-with-style/index.html new file mode 100644 index 0000000..0a49672 --- /dev/null +++ b/Scratch/en/blog/2009-10-How-to-preload-your-site-with-style/index.html @@ -0,0 +1,192 @@ + + + + + + YBlog - How to preload your site with style + + + + + + + + + + + + + +
+ + +
+

How to preload your site with style

+
+
+
+
+

Example

+

Here is a live example of what appear while loading my pages.

+
+

+Hello! I’ve finished loading! +

+

+Click me to see me disapear again. +

+
+ +Loading… loading logo +
+ +
+ +

I first tried to integrate queryLoader, but it didn’t fill my needs.

+

The plugin add a black div to hide all the content. But as the script had to be launched at the end of the source code my website show for a small time.

+

In order to hide this small artefact, here is how I do that.

+

Code

+

In a first time, I added at the top of the body the div hiding all the content.

+
+
...
+<body>
+<div id="blackpage">
+    content to display during the loading.
+</div>
+...
+
+ +

and here is the associated CSS to #blackpage:

+
+
#blackpage
+  top: 0 
+  left: 0 
+  width: 100%
+  height: 100%
+  margin-left: 0
+  margin-right: 0
+  margin-top: 0
+  margin-bottom: 0
+  position: absolute
+  text-align: center
+  color: #666
+  padding-top: 10em
+  background-color: #eee
+  z-index: 9000
+
+ +

and the associated jQuery code:

+
+
$(document).ready(function(){
+    $('#blackpage').fadeOut();
+});
+
+ +

Yes, it is as simple as that. And, putting the #blackpage div at the top of my page, I ensure to hide anything while loading.

+

I hope it had helped you!

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-10-03 +
+ +
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2009-10-Wait-to-hide-a-menu-in-jQuery/index.html b/Scratch/en/blog/2009-10-Wait-to-hide-a-menu-in-jQuery/index.html new file mode 100644 index 0000000..496080e --- /dev/null +++ b/Scratch/en/blog/2009-10-Wait-to-hide-a-menu-in-jQuery/index.html @@ -0,0 +1,170 @@ + + + + + + YBlog - Menu waiting to hide himself + + + + + + + + + + + + + +
+ + +
+

Menu waiting to hide himself

+
+
+
+
+

I discussed earlier why I prefer to hide my navigation menu. I finally decided to hide it only after a short time. Just the time needed for a user to see it. But how make it disappear only when it is not used for some time?

+

Here is how to accomplish that easily.

+

HTML:

+
+
    <div id="menuButton"></div>
+    <div id="entete">
+        <ul>
+            <li> menu item 1 </li>
+            ...
+            <li> menu item n </li>
+        </ul>
+    </div>
+
+ +

CSS:

+
+

#entete { top: 1em; left: 0; position: fixed; width: 10em; z-index: 2000; }

+
#entete {
+  top: 1em;
+  height: 22em;
+  left: 0;
+  position: fixed;
+  width: 10em; }
+
+
+ +

Javascript:

+
+

var last=0;

+

// will hide the menu in 5 seconds // if the variable ‘last’ has not changed its value function autoHideMenu(value) { setTimeout(function(){ if ( last == value ) { hideMenu(); } },5000); }

+

$(document).ready( function() { // show the menu when the mouse is on // the good area $(‘#menuButton’).hover(showMenu);

+
// If the mouse is on the menu change the
+// value of 'last'
+// try to hide the menu when the mouse 
+// go out off the menu.
+$('#entete').hover(
+    function(){last+=1;}, 
+    function(){autoHideMenu(last);} );
+autoHideMenu(0);
+

});

+

// show / hide menu functions details

+

// move to the left function hideMenu() { $(‘#entete’).animate({left:“-10em”}, 500 ); }

+

// move to right and will try to hide in 5 sec. function showMenu() { $(‘#entete’).animate({left:“0em”}, 500 ); last+=1; autoHideMenu(last); }

+
+
+ +

Simple and lightweight. No timer (almost), no memory leak, no Date…

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-10-26 +
+ +
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2009-10-launch-daemon-from-command-line/index.html b/Scratch/en/blog/2009-10-launch-daemon-from-command-line/index.html new file mode 100644 index 0000000..2497c7f --- /dev/null +++ b/Scratch/en/blog/2009-10-launch-daemon-from-command-line/index.html @@ -0,0 +1,130 @@ + + + + + + YBlog - launch daemon from command line + + + + + + + + + + + + + +
+ + +
+

launch daemon from command line

+
+
+
+
+

Here is a tip, I don’t know why, but I almost always forgot how to do that.

+

When you want to launch a command and this command should not be killed after you close your terminal. Here is how to accomplish that from command line:

+
+ nohup cmd & ~~~~~~ where cmd is your command. +
+ +

I let this command here for me and I wish it could also help someone.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-10-23 +
+ +
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2009-10-untaught-git-usage/index.html b/Scratch/en/blog/2009-10-untaught-git-usage/index.html new file mode 100644 index 0000000..e08b5b5 --- /dev/null +++ b/Scratch/en/blog/2009-10-untaught-git-usage/index.html @@ -0,0 +1,258 @@ + + + + + + YBlog - Untaught Git usage + + + + + + + + + + + + + +
+ + +
+

Untaught Git usage

+
+
+
+
+

I explain why I had so much difficulties to use Git. There is an “untaught rule” that make hard to work without. Until I read the good document.

+

“Cheap branches” aren’t designed to be totally isolated branches but rather should follow a “Master Branch”. There is a Standard Workflow to follow. If you don’t follow it, you prepare yourself with some hard time with Git.

+
+

My way to decentralisation

+

From SVN to Bazaar

+

I was a huge user of subversion (svn). Until the day I saw this video of Linus Torvald. Where he explain Git and all advantages of Decentralized Concurrent Versioning System(DCVS)

+

I must say I was completely convinced. And the more you learn about DCVS the more you see good reason to use them.

+

I then needed a versioning system for my team. As they were not used to open source versioning system except those heavy, with a GUI and with and administrator

+

After some web searches, I founded three main choices:

+ +

After trying each other I chosen Bazaar. It has the simplest User Interface*. My choice was done.

+

From Bazaar to Git

+

It was really natural to learn when coming from subversion. The pull command corresponding to update, push command to commit. Commands like commit and update are still there if you want to use an SVN workflow.

+

After some times, reading on many blogs, I realize Git is far more popular and by influent people.

+

I then decide to use Git in particular to version this current website. But after trying it, I found it difficult and couter intuitive (I’ll speak a work about it later).

+

After calling for some help, when I say Bazaar is much simpler to learn, some people answer me that Git:

+
+

SO-MUCH-EASY my 12 year old daughter uses it to version its school documents. She has no difficulties at all, creating branches, blah, blah, blah…

+
+

If a 12 years old girl has no problem with Git and I (with my Computer Science Ph.D.) have difficulties to uses it like I want, it is frustrating and humiliating. But what makes Git natural for some people and confusing for me?

+

I finally understood why reading a document I didn’t read before. It was the untaught part of the conception. The part every developer found so natural it is not necessary to say a word about it. But it was not natural for me.

+

- I speak about ClearCase(c). I know there exists command line tools. But it was not the way my team used it.

+

* - I never really given its chance to Mercurial. The terminology they chosen was too far from the svn one. And I was used to it.

+
+

When you see explanation about branches and DCVS we imagine each branch is totally uncorrelated to each other, except when merging. Everything is magic. This is the “Parallel World” explanation. This way of seeing is explained more in depth in the real good article about branches on betterexplained.

+

Git was designed to manage the Linux Kernel. Git was designed using the concept of Patch instead of Parallel Worlds.

+

From one site Parallel World and Patches from the other. There is many equivalent notions in the two point of vue, but also some differences.

+
    +
  • Bazaar seems base on the Parallel World vision which implies Patches
  • +
  • While Git seem base on the Patch model which will implie the creation of Parallel Worlds.
  • +
+

I will not argument about which is the best. Just tell my vision of DCVS come from the Parallel World vision and Git was designed the other way.

+

From Theory to Real Life Usage

+

I believe I understood conceptual mechanism under Git. But I had some difficulties with real usage. The worst point, the one I didn’t get before long was because I didn’t get really well the notion of Cheap Branching.

+

What is a Cheap Branch? If like me you come from Bazaar, it is a totally new notion. It is in fact the ability to create a branches all of them using the same directory.

+

You just have to launch a Git command and the local directory reflect the state of the branch you selected.

+

In theory, Cheap Branches are exactly like Bazaar branches. The word used is Branch and not Cheap Branch. But there is a slight difference between them. A slight difference between a Cloned Branch and a Cheap Branch.

+

A “Standard branch” is what is theoretically a kind of new Parallel World. But Cheap branch was designed to be future Patch for the main branch of the directory/Cloned branch.

+

Of course, I know anybody can state you can totally use Cheap branches as Cloned branches. But they weren’t designed for that. On daily usage, it is a bit uneasy to use it like this.

+

Here how Git cheap branches should be used (for more details see Git for Designers):

+
    +
  • get or creation of a main repositoy The Great Repository
  • +
  • creation of a Cheap branch containing differences which have to be patched somewhere in the future into The Great Repository
  • +
+

Here’s how you should not use Git:

+
    +
  • Get or creation of a repository
  • +
  • Create a cheap branch which will never push it’s modification to the main repository.
  • +
+

This simple minor difference of point of view confused me a lot.

+

Real Life Usage

+

Now I have understood all that. I understand why Git has some many people claiming it is the best DCVS.

+

Cheap branching notion is essential in Git and is a really useful feature. Particularly for this website. But, there are not exactly, completely parallel line of development. Because they are designed to path the main branch. Of course, it is not an obligation, but there are slight messages which tell you this should be used like that.

+

If I want to separate in a better way some branches I just have to Clone them. And I return exactly in branches Bazaar provided me.

+

Examples

+

For now, I prefer (from far) Bazaar terminology. They are cleaner and more understandable.

+
+bzr revert +
+ +

Is clearer than

+
+git reset –hard HEAD +
+ +

We can tell the same thing about

+
+bzr revert -r -3 +
+ +

which seems preferable to

+
+git reset –hard HEAD~3 +
+ +

Until now, it is not big business. But now, things will go worse. If we want to revert time on all the tree we use the keyword reset.

+
+OK +
+ +

Now, if I want to revert time on on file. We should naturally imagine the command will be:

+
+git reset –hard FILE +
+ +
+OF COURSE NOT! +
+ +

The solution is:

+
+git checkout FILE +
+ +

What? checkout !? Well, ok. I accept. why not? With Bazaar it is:

+
+git revert FILE +
+ +

What I personally found far more natural.

+

But the command to change the current cheap branch is really hard to be accepted (from the User Interface point of view). With Bazaar it is:

+
+cd ../branch +
+ +

Well yes. With Bazaar you have to change your directory to change your branch. It needs more disk resources but it is really clear. Which is my current branch, is just a pwd away. For Git here is the command:

+
+git checkout branch +
+ +

WTF? I believed checkout was the key to get a file in some state (not the entire tree).

+

Then checkout is the same keyword used to get back in time on a file (BUT NOT ON ALL THE TREE where you have to use reset --hard) and to change current branch!

+

It is totally unnatural. Even if it is theoretically totally justified like you can see in the really good article Git for Computer Scientist. From the user point of vue, it is difficult to do worse than that. It is like somebody made it on purpose to make it the hardest possible to learn and understand.

+
+
    +
  • — Try to find the good keyword for this operation
  • +
  • — Wrong! Try again!
  • +
  • — False, it is not yet right!
  • +
+
+

That were the Git bad side. But It has many advantages. Once you’ve understood the cheap branching paradigm. All became clearer for me after. Even if there is also some difficulties with the edit of the .git/config files (not user friendly at all).

+

I must precise that I worked a lot with multi-modal logic and particularly about “Temporal Logics” (linear or not). This is why I was more inclined to see things this way. “Ah ! Just to remember my firsts love with computer science !”

+
+

Conclusion

+

DCVS vs. CVS ?

+

Was it a good idea to change to a decentralised versionning system? Clearly yes. Decentralisation give far much great possibilities. Such as working on a fix on a totally isolated branches.

+

Is Git better than Bazaar?

+

Speaking about features I’ll tell Git is the best. But Git was too much in my way. Is was exactly what I didn’t want for my first DCVS.

+

I shouldn’t have had those difficulties about understanding cheap branching which must be a patch. In reality, Git make a difference between the Tree and the Branch. Which is obviously not the case for Bazaar. Conceptually, bazaar is simpler to understand.

+

Finally

+

In conclusion, I use Git more often than Bazaar and I must say, that I have some preferences for Git. However, Git lack hardly clear commands name like revert. For now I don’t made alias to correct that. But may be one day I should do that.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-10-13 +
+ +
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2009-11-12-Git-for-n00b/code/gitconfig b/Scratch/en/blog/2009-11-12-Git-for-n00b/code/gitconfig new file mode 100644 index 0000000..828ac4d --- /dev/null +++ b/Scratch/en/blog/2009-11-12-Git-for-n00b/code/gitconfig @@ -0,0 +1,19 @@ +[color] + branch = auto + diff = auto + status = auto +[alias] + st = status + co = checkout + br = branch + lg = log --pretty=oneline --graph + logfull = log --pretty=fuller --graph --stat -p + unstage = reset HEAD + # there should be an article on what this command do + uncommit = !zsh -c '"if (($0)); then nb=$(( $0 - 1 )); else nb=0; fi; i=0; while ((i<=nb)); do git revert -n --no-edit HEAD~$i; ((i++)); done; git commit -m \"revert to $0 version(s) back\""' + undomerge = reset --hard ORIG_HEAD + conflict = !gitk --left-right HEAD...MERGE_HEAD + # under Mac OS X, you should use gitx instead + # conflict = !gitx --left-right HEAD...MERGE_HEAD +[branch] + autosetupmerge = true diff --git a/Scratch/en/blog/2009-11-12-Git-for-n00b/index.html b/Scratch/en/blog/2009-11-12-Git-for-n00b/index.html new file mode 100644 index 0000000..aad3bf1 --- /dev/null +++ b/Scratch/en/blog/2009-11-12-Git-for-n00b/index.html @@ -0,0 +1,505 @@ + + + + + + YBlog - Git for n00b + + + + + + + + + + + + + +
+ + +
+

Git for n00b

+
+
+
+
+
+ +

A detailed tutorial of Git for people knowing very few about versions systems. You’ll understand utility of such program and how we use modern version control system. I try to stay as pragmatic as possible.

+
+ +
+

Begin with conclusion

+

Here is the list of sufficient and necessary command to use Git. There is very few. It is normal not to understand immediately but it is to gives you an idea. Even if this article is long, 95% of Git usage is in these 7 commands:

+

Get a project from the web:

+
git clone ssh://server/path/to/project
+

Everyday Git usage:

+
# get modifications from other
+git pull
+# read what was done
+git log
+
+# Make local changes to files 
+hack, hack, hack...
+# list the modified files
+git status
+# show what I've done
+git diff
+
+# tell git to version a new file
+git add new/file
+
+# commit its own modifications 
+# to its local branch
+git commit -a -m "Fix bug #321"
+
+# send local modifications to other
+git push
+

This article is written for people knowing very few about versionning systems. It is also written for those who had didn’t followed progress since CVS or subversion (SVN). This is why, in a first time I’ll explain quickly which are the goal of such systems. Secondly, I’ll explain how to install and configure Git. Then, I give the command for each feature a DCVS must have.

+

Git for what?

+
+ +

If you just want to use Git immediately, just read dark part. You read this part later to understand correctly foundations of version systems and not doing strange things.

+
+ +

Git is a DCVS, which means a Decentralized Concurrent Versions System. Let’s analyze each part of this long term:

+

Versions System

+

Firstly, versions system manage files. When somebody work with files without a versions system, the following happens frequently:

+

When you modify a somehow critical file you don’t want to loose. You copy naturally this file with another name. For example:

+
$ cp fichier_important.c fichier_important.c.bak
+

In consequence of what, the new file, play the role of backup. If you break everything, you can always return in the last state by overwriting your modifications. Of course, this method is not very professional and is a bit limited. If you make many modifications, you’ll end with many files with strange names like:

+
+
fichier_important.c.bak
+fichier_important.c.old
+fichier_important.c.Bakcup
+fichier_important.c.BAK.2009-11-14
+fichier_important.c.2009.11.14
+fichier_important.c.12112009
+old.fichier_important.c
+
+ +

If you want to make it works correctly, you’ll have to use naming convention. Files take many place even if you modify most of time only some lines.

+

Fortunately, versions system are here to help.

+

You only have to signal you want a new version of a file and the versions system will do the job for you. It will record the backup where it could be easily recovered. Generally, systems version do it better than you, making the backup only of the modified lines and not the total file.

+

Once upon a time versions were managed for each file separately. I think about CVS. Then it naturally appears projects are a coherent set of files. Recover each file separately was a tedious work. This is why versions number passed from files to the entire project.

+

It is therefore possible to say, “I want to get back three days earlier”.

+
+ +

What gives versions system? (I didn’t mention everything at all)

+
    +
  • automatic backups: back in time,
  • +
  • gives the ability to see differences between each version,
  • +
  • put a tag on some version to be able to refer to them easily,
  • +
  • gives the ability to see an historic of all modifications. Generally the user must add a comment for each new version.
  • +
+
+ +

concurrent:

+

Version Systems are already useful to manage its own projects. They help to organize and resolve partially backup problems. I say partially because you have to backup your repository on a decent file system. But versions system are really interesting is on projects done by many people.

+

Let’s begin by an example, a two person project ; Alex and Beatrice. On a file containing a Lovecraft’s gods list:

+
+
Cthulhu
+Shubniggurath
+Yogsototh
+
+ +Say Alex is home and modify the file: +
+
+Cthulhu
+Shubniggurath
+Soggoth
+Yogsototh
+
+
+ +

after that he send the file on the project server. Then on the server there is the Alex file:

+

A bit later, Beatrice who had not get the Alex file on the server make the modification:

+
+
+Cthulhu
+Dagon
+Shubniggurath
+Yogsototh
+
+
+ +

Beatrice send her file on the server

+

Alex modification is lost. One more time, versions system are here to help.

+

A version system would had merge the two files at the time Beatrice send the file on the server. And like by magic, on the server the file would be:

+
+
+Cthulhu
+Dagon
+Shubniggurath
+Soggoth
+Yogsototh
+
+
+ +

In real life, at the moment Beatrice want to send her modifications, the versions system alert her a modification had occurred on the server. Then she uses a command which pull the modification from the server to her local computer. And this command update her file. After that, Beatrice send again the new file on the server.

+
+ +

In what Concurrent Versions System help?

+
    +
  • get without any problem others modifications,
  • +
  • send without any problem its own modifications to others,
  • +
  • manage conflicts. I didn’t speak about it, but sometimes a conflict can occur (when two different people modify the same line on a file for example). SVC help to resolve such problem. More on that later,
  • +
  • help to know who done what and when.
  • +
+
+ +

decentralized

+

This word became popular only recently about CVS. And it mainly means two things:

+

First, until really recently (SVN), you’ll have to be connected to the distant server to get informations about a project. Like get the history. New decentralized systems work with a local REPOSITORY (directory containing backups and many informations linked to the versions system functionalities). Hence, one can view the history of a project without the need of being connected.

+

All instances of a project can live independently.

+

To be more precise, DCVS are base on the branch notion.

+

Practically, it has great importance. It means, everybody work separately, and the system help to glue all their work.

+

It is even more than just that. It help to code independently each feature and bug fixes. Under other system it was far more difficult.

+

Typical example:

+
+

I develop my project. I’m ameliorating something. An urgent bug is reported.

+

With a DCVS I can easily, get back to the version with the bug. Fix it. Send the fix. Get back to my feature work. And even, use the fix for the new version with my new feature.

+

In a not decentralized version system, doing such a thing is possible but not natural. Decentralization means it become natural to use a branch for each separable work.

+
+
+ +

Advantages given by DCVS:

+
    +
  • Ability to work offline,
  • +
  • Ability to create many atomic patches,
  • +
  • Help the maintenance of many different versions of the same application.
  • +
+
+ +

To resume

+

Let’s resume what we can easily do with DCVS:

+

Versions Systems

+
    +
  • back in time,
  • +
  • list differences between versions,
  • +
  • name some versions to refer to them easily
  • +
  • show history of modifications
  • +
+

Concurrent

+
    +
  • get others modifications,
  • +
  • send its modifications to others,
  • +
  • know who done what and when,
  • +
  • conflicts management.
  • +
+

Decentralized

+
    +
  • Easily manipulate branches
  • +
+

Now let’s see how to obtain all these things easily with Git.

+

Before usage, configuration

+

install

+

Under Linux Ubuntu or Debian:

+
$ sudo apt-get install git
+

Under Mac OS X:

+ +
$ sudo port selfupdate
+
+$ sudo port install git-core
+

Global configuration

+

Save the following file as your ~/.gitconfig.

+
[color]
+    branch = auto
+    diff   = auto
+    status = auto
+[alias]
+    st        = status
+    co        = checkout
+    br        = branch
+    lg        = log --pretty=oneline --graph
+    logfull   = log --pretty=fuller --graph --stat -p
+    unstage   = reset HEAD
+    # there should be an article on what this command do
+    uncommit = !zsh -c '"if (($0)); then nb=$(( $0 - 1 )); else nb=0; fi; i=0; while ((i<=nb)); do git revert -n --no-edit HEAD~$i; ((i++)); done; git commit -m \"revert to $0 version(s) back\""'
+    undomerge = reset --hard ORIG_HEAD
+	conflict  = !gitk --left-right HEAD...MERGE_HEAD
+    # under Mac OS X, you should use gitx instead
+	# conflict    = !gitx --left-right HEAD...MERGE_HEAD
+[branch]
+	autosetupmerge = true
+

You can achieve the same result using for each entry the command: git config --global. Next, configure your name and your email. For example, if your name is John Doe and your email is john.doe@email.com. Launch the following commands:

+
$ git config --global user.name John Doe
+
+$ git config --global user.email john.doe@email.com
+

Here it is. Base configuration is over. The file containing alias will help to type shorter commands.

+

Get a project

+

If a project is already versionned with Git you should have an URL of the sources. Then use the following command:

+
$ cd ~/Projets
+$ git clone git://main.server/path/to/file
+

If there is no git server but you’ve got an ssh access. Just replace the git://host by ssh://user@host. In order not to type your password each time, use:

+
$ ssh-keygen -t rsa
+

Reply to question and do not enter a password. Then copy your keys to the distant server. This is not the safest way to do this. The safest being, using ssh-agent.

+

The easiest way if you have ssh-copy-id:

+
me@locahost$ ssh-copy-id ~/.ssh/id_rsa.pub me@main.server
+

or manually

+
me@locahost$ scp ~/.ssh/id_rsa.pub me@main.server:
+me@locahost$ ssh me@main.server
+password:
+me@main.server$ cat id_rsa.pub >> ~/.ssh/authorized_keys
+me@main.server$ rm id_rsa.pub
+me@main.server$ logout
+

Now you don’t need to write your password to access the main.server.

+

Creating a new project

+

Suppose you already have a project with files. Then it is really easy to version it.

+
$ cd /path/to/project
+$ git init
+$ git add .
+$ git commit -m "Initial commit"
+

Let do a small remark. If you don’t want to version every file. Typically intermediate compilation file, swap files… Then you need to exclude them. Just before launching the git add . command. You need to create a .gitignore file in the root directory of your project. This file will contain all exclude pattern. For example:

+
*.o
+*.bak
+*.swp
+*~
+

Now, if you want to create a repository on a distant server, it must not be in bare mode. The repository will contain only versionning informations, but not the files of the project. To achieve that:

+
$ cd /path/to/local/project
+$ git clone --bare . ssh://server/path/to/project
+

Others will be able to get your modifications.

+
git clone ssh://server/path/to/project
+

Abstract of the second step

+

You now have a local directory on your computer. It is versionned and you can say it is, because there is a .git directory at the root (and the root only) of your project. This directory contain all necessary informations for Git to version your project.

+

Now you only need to know how to use it.

+

Here we go!

+

Here is one from many way to use Git. This method is sufficient to work on a project. Not there is many other workflows.

+

Basic usage

+

Work with Git immediately:

+
    +
  • Get modification done by others git pull,
  • +
  • See details of these modifications git log,
  • +
  • Many times:
  • +
  • Make an atomic modification
  • +
  • Verify details of this modification: git status and git diff,
  • +
  • Add some file to be versionned if necessary:
    git add [file],
  • +
  • Save you modifications
    git commit -a -m "message",
  • +
  • Send your modifications to others: git push (redo a git pull if push return an error).
  • +
+

With these few commands you can use Git. Even if it is sufficient, you need to know one more thing before really begin ; How to manage conflicts.

+

Conflicts management

+

Conflicts can arise when you change the same line of code on the same file from another branch you’re merging. It can seems a bit intimidating, but with Git this kind of thing is really simple to handle.

+

example

+

You start from the following file

+
+
Zoot 
+
+ +

and you modify one line

+
+
+Zoot the pure
+
+
+ +

except during this time, another user had also modified the same line and had done a push.

+
+
+Zoot, just Zoot
+
+
+ +

Now when you do a:

+
+
$ git pull
+remote: Counting objects: 5, done.
+remote: Total 3 (delta 0), reused 0 (delta 0)
+Unpacking objects: 100% (3/3), done.
+From /home/yogsototh/tmp/conflictTest
+   d3ea395..2dc7ffb  master     -> origin/master
+Auto-merging foo
+CONFLICT (content): Merge conflict in foo
+Automatic merge failed; fix conflicts and then commit the result.
+
+ +

Our file foo now contains:

+
+
+<<<<<<< HEAD:foo
+Zoot the pure
+=======
+Zoot, just Zoot
+>>>>>>> 2dc7ffb0f186a407a1814d1a62684342cd54e7d6:foo
+
+
+ +

Conflict resolution

+

To resolve the conflict you only have to edit the file for example, writing:

+
+
+Zoot the not so pure
+
+
+ +

and to commit

+
+
git commit -a -m "conflict resolved"
+
+ +

Now you’re ready to use Git. Git provide many other functionnalities. Now we’ll see some Git usages older CVS couldn’t handle.

+

Why Git is cool?

+

Because with Git you can work on many part of some project totally independently. This is the true efficiency of decentralisation.

+

Each branch use the same directory. Then you can easily change your branch. You can also change branch when some files are modified. You can then dispatch your work on many different branches and merge them on one master branch at will.

+

Using the git rebase you can decide which modifications should be forget or merged into only one modification.

+

What does it mean for real usage? You can focus on coding. For example, you can code, a fix for bug b01 and for bug b02 and code a feature f03. Once finished you can create a branch by bug and by feature. And finally you can merge these modifications on a main branch.

+

All was done to code and decide how to organize your versions after. In other VCS it is not as natural as in Git.

+

With Git you can depend of many different sources. Then, there is not necessarily a ‘master’ repository where everybody puts its modifications.

+

What changes the most with Git when you come from SVN, it’s the idea of a centralized project on one server. With Git many people could work on the same project but not necessarily on the same repository as main reference. One can easily fix a bug and send a patch to many different versions of a project.

+

Command List

+

Command for each functionality

+

In the first part, we saw the list of resolved problem by Git. To resume Git should do:

+
    +
  • get others modifications,
  • +
  • send modifications to others,
  • +
  • get back in time,
  • +
  • list differences between each version,
  • +
  • name some versions in order to refer easily to them,
  • +
  • write an historic of modifications,
  • +
  • know who did what and when,
  • +
  • manage conflicts,
  • +
  • easily manage branches.
  • +
+

get others modifications

+
$ git pull
+

send modifications to others

+
$ git push
+

or more generally

+
$ git pull
+$ git push
+

get back in time

+

For all tree

+
$ git checkout
+
$ git revert
+

revert three version before (see my .gitconfig file).

+
$ git uncommit 3
+

Undo the las merge (if something goes wrong)

+
$ git revertbeforemerge
+

For one file

+
$ git checkout file
+$ git checkout VersionHash file
+$ git checkout HEAD~3 file
+

list differences between each version

+

list files being modified

+
$ git status
+

differences between last version files and local files

+
$ git diff
+

differences between some version and local files

+
$ git diff VersionHash fichier
+

name some version to refer to them in the future

+
$ git tag 'toto'
+

show historic of modifications

+
$ git log
+$ git lg
+$ git logfull
+

know who did what and when

+
$ git blame fichier
+

handle conflicts

+
$ git conflict
+

manage branches

+

To create a branch:

+
$ git branch branch_name
+

To change the current branch:

+
$ git checkout branch_name
+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-11-12 +
+ +
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2009-12-06-iphone-call-filter/index.html b/Scratch/en/blog/2009-12-06-iphone-call-filter/index.html new file mode 100644 index 0000000..bb9ec34 --- /dev/null +++ b/Scratch/en/blog/2009-12-06-iphone-call-filter/index.html @@ -0,0 +1,125 @@ + + + + + + YBlog - iphone call filter + + + + + + + + + + + + + +
+ + +
+

iphone call filter

+
+
+
+
+

It is unbelievable you cannot filter your call with an iPhone! The only reason I see for that is a negotiation with phone operator to force users to get phone advertising. It is simple unacceptable.

+

I’m a λ iPhone’s user. The only way to filter your call and to manage blacklist is to jailbreak your iPhone. And I don’t want to do that. Then, if like me you find it unacceptable, just write a line to Apple: http://www.apple.com/feedback/iphone.html

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-12-06 +
+ +
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2009-12-14-Git-vs--Bzr/code/gitconfig b/Scratch/en/blog/2009-12-14-Git-vs--Bzr/code/gitconfig new file mode 100644 index 0000000..78a9edd --- /dev/null +++ b/Scratch/en/blog/2009-12-14-Git-vs--Bzr/code/gitconfig @@ -0,0 +1,2 @@ +[alias] + uncommit = !zsh -c '"if (($0)); then nb=$(( $0 - 1 )); else nb=0; fi; i=0; while ((i<=nb)); do git revert -n --no-edit HEAD~$i; ((i++)); done; git commit -m \"revert to $0 version(s) back\""' diff --git a/Scratch/en/blog/2009-12-14-Git-vs--Bzr/index.html b/Scratch/en/blog/2009-12-14-Git-vs--Bzr/index.html new file mode 100644 index 0000000..9285de6 --- /dev/null +++ b/Scratch/en/blog/2009-12-14-Git-vs--Bzr/index.html @@ -0,0 +1,208 @@ + + + + + + YBlog - Git vs. Bzr + + + + + + + + + + + + + +
+ + +
+

Git vs. Bzr

+
+
+
+
+
+ +

Why even if I believe git has many bad point I believe it is the best DCVS around to work with. This is why I first tell why I prefer Bazaar over Git. Secondly I’ll talk about the only advantage of git against Bazaar which lead me to prefer it.

+
+ +

The DCVS discovery

+

Before beginning this article, you should know I come from subversion. I find subversion to be a really good CVS. But I was converted to the decentralized ones.

+

There is two way of perceive version control system. Either you think in term of branches (see the really good article on betterexplained) or think in term of patches. Another way to say that, is weather you concentrate on vertices or on transitions of the graph of possible states of your project.

+

This is the second approach who was behind git and this is the first behind Bazaar. git was created by Linus Torvald in order to close some gap in the version system used to develop the Linux kernel. And patches is a term which is more present than ‘state’ in the development community.

+

I first was convinced by Bazaar. Why? Argument in favor of Bazaar were: user friendly, terminology close to the subversion one. And I tried a bit the two, and it was clearly more natural for me to use Bazaar. But after seeing so many people using git I decided to give it a serious try.

+

And it was so fastidious! The git terminology was horrible! And it is nothing to say it.

+

Where Bazaar is better than git

+

The first example, checkout is used to make only one thing from the technical point of vue. But from the user perspective, you make many different things with this word. Example:

+
+ git checkout pipo +
+ +

undo the current modification of the file pipo

+
+ git checkout pipo +
+ +

change the current branch to the branch pipo

+

And, like me, you remark, it is exactly the same command to make two completely different things. What occur when you have a pipo branch and a pipo file? By default, it change the current branch. In order to leave the ambiguity you have to use the following syntax:

+
+ git checkout ./pipo +
+ +

Yes, hum…

+

It works, but it is clearly not really user friendly. Furthermore, checkout had a complete different signification in older CSV like cvs et svn. checkout was used to get a distant project locally.

+

Bazaar terminology is far more natural, because there is no command to change the current branch as there is only one branch per directory. Changing a branch in Bazaar is changing the current directory. I also believe it is the biggest problem of Bazaar, I’ll tell you why. And to undo things in Bazaar:

+
+ bzr revert pipo +
+ +

Furthermore, most Bazaar command take a revision number in parameter. For example, to get back 3 versions earlier, it is enough to write:

+
+ bzr revert -r -3 pipo +
+ +

The git equivalent is far more cryptic:

+
+ bzr checkout HEAD~3 pipo +
+ +

One more time, Bazaar is far more readable.

+

Back in time for all the project:

+

with Bazaar:

+
+ bzr revert -r -3 pipo +
+ +

and with git? git checkout? Of course not! It would be too simple. What we find in the documentation (man) and everywhere on the net:

+
+ git reset –hard HEAD~3 +
+ +

Except that this command is horrible. It forget revisions! Then you must use it with prudence. And you cannot tell other people working on the project you discard some changes. If someone had pulled the bad version, you are doomed. This is why you can also use:

+
+ git checkout HEAD~3 – . && git commit -m ‘back in time’ +
+ +

Just to keep a backup branch. Without it we can definitively loose the current version HEAD. But some error may rest when there were some addition and deletion of files. The unique way to be really clean without any risk is to use the following command:

+
+ for i in (seq02); dogitrevert − n −  − no − editheadi; done git commit -m “reverted 3 versions back” +
+ +

And with this command this is the only good way to undo things in a project and tell other contributor you reverted something. You simply revert version in backward order.

+

The rule is simple: NEVER use the git reset command on a version somebody else could have fetched

+

It was said. Discover the best method took me some time. I’d made many different tries. The safer and best way of reverting back your tree is to use this method. If you want to make it automatic just had the following alias in your ~/.gitconfig. Of course this alias will work only on environment having zsh installed. Which is the cas for most UNIX (Ubuntu, Mac OS X…).

+
+ [alias] uncommit = !zsh -c ‘“if ((0)); thennb = (( 0 − 1)); elsenb = 0; fi; i = 0; while((i <  = nb)); dogitrevert − n −  − no − editHEADi; ((i++)); done; git commit -m "revert to $0 version(s) back"”’ +
+ +

What make git by far the best DCVS today

+

After talking about the negatives points of git, now it’s time to speak about the very positive feature that make git the best DCVS in my humble opinion.

+

Cheap branching

+

You always work into the same main directory. For example, you can work on two fix in the same time. Say fix1 require you to work on file1 and fix2 to work on file2. You can work in any order on file1 and file2 in the master branch. And then go to branch fix1, commit file1 into it. Then go to branch fix2 and commit file2 into it. And finally merge the two branches fix1 and fix2 into master.

+
+ > vim file1 > vim file2 > git br fix1 > git add file1 > git commit -m ‘fix1’ > git br fix2 > git add file2 > git commit -m ‘fix2’ > git commit master > git merge fix1 > git merge fix2 +
+ +

And this is great not to worry about working in the good branch and coding in the same time. You just worry about your code and then about the versionning system.

+

And I use this possibilities a lot. Working with bazaar, I often made the error to begin a change in the bad branch. then I have to copy my modifications, then revert. In short it was tiedous.

+

This is why I prefer using git on an every day usage. If Bazaar implement the same way of cheap branching than git. I should switch again.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2009-12-14 +
+ +
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2010-01-04-Change-default-shell-on-Mac-OS-X/index.html b/Scratch/en/blog/2010-01-04-Change-default-shell-on-Mac-OS-X/index.html new file mode 100644 index 0000000..f8670f7 --- /dev/null +++ b/Scratch/en/blog/2010-01-04-Change-default-shell-on-Mac-OS-X/index.html @@ -0,0 +1,129 @@ + + + + + + YBlog - Change default shell on Mac OS X + + + + + + + + + + + + + +
+ + +
+

Change default shell on Mac OS X

+
+
+
+
+

I just found a way to change the default shell on Mac OS X. This note is mostly for me, but somebody else should find it useful. Just launch the following command:

+
+ > chsh +
+ + +
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2010-01-04 +
+ +
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/code/local.conf b/Scratch/en/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/code/local.conf new file mode 100644 index 0000000..f1a5dfc --- /dev/null +++ b/Scratch/en/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/code/local.conf @@ -0,0 +1,54 @@ + + + + + + + +misc.conf + + + +alias.conf + + + +msfonts-rules.conf + + + + Tahoma + + + Verdana + + + + + + + Lucida Grande + + + + + + + + Georgia + + + Georgia + + + + + + + Century Schoolbook L + + + + + + diff --git a/Scratch/en/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/index.html b/Scratch/en/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/index.html new file mode 100644 index 0000000..fd9214e --- /dev/null +++ b/Scratch/en/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/index.html @@ -0,0 +1,183 @@ + + + + + + YBlog - antialias font in Firefox under Ubuntu + + + + + + + + + + + + + +
+ + +
+

antialias font in Firefox under Ubuntu

+
+
+
+
+

How to stop using bad Microsoft© font under Ubuntu Linux in order to user nice anti aliased font under Firefox.

+

Just modify the /etc/fonts/local.conf with the following code:

+
+

+<?xml version="1.0"?>
+<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+<fontconfig>
+
+<!-- Miscellaneous settings -->
+
+<include ignore_missing="yes">misc.conf</include>
+
+<!-- Define alias -->
+
+<include ignore_missing="yes">alias.conf</include>
+
+<!-- Rules for Microsoft fonts -->
+
+<include ignore_missing="yes">msfonts-rules.conf</include>
+
+  <match target="pattern" name="family" >
+      <test name="family" qual="any" >
+          <string>Tahoma</string>
+      </test>
+      <edit mode="assign" name="family" >
+          <string>Verdana</string>
+      </edit>
+  </match>
+  <selectfont>
+      <acceptfont>
+          <pattern>
+              <patelt name="family"> 
+                <string>Lucida Grande</string> 
+              </patelt>
+          </pattern>
+      </acceptfont>
+  </selectfont>
+
+  <match target="pattern" name="family" >
+      <test name="family" qual="any" >
+          <string>Georgia</string>
+      </test>
+      <edit mode="assign" name="family" >
+          <string>Georgia</string>
+      </edit>
+  </match>
+  <selectfont>
+      <acceptfont>
+          <pattern>
+              <patelt name="family"> 
+                <string>Century Schoolbook L</string> 
+              </patelt>
+          </pattern>
+      </acceptfont>
+  </selectfont>
+
+</fontconfig>
+
+ +

Hope it helped someone who like me had his eyes crying in face of such ugly fonts.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2010-01-12 +
+ +
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2010-02-15-All-but-something-regexp/index.html b/Scratch/en/blog/2010-02-15-All-but-something-regexp/index.html new file mode 100644 index 0000000..907e3a5 --- /dev/null +++ b/Scratch/en/blog/2010-02-15-All-but-something-regexp/index.html @@ -0,0 +1,156 @@ + + + + + + YBlog - Pragmatic Regular Expression Exclude + + + + + + + + + + + + + +
+ + +
+

Pragmatic Regular Expression Exclude

+
+
+
+
+

Sometimes you cannot simply write:

+
+ if str.match(regexp) and not str.match(other_regexp) do_something +
+ +

and you have to make this behaviour with only one regular expression. But, there exists a major problem: the complementary of a regular language might not be regular. Then, for some expression it is absolutely impossible to negate a regular expression.

+

But sometimes with some simple regular expression it should be possible. Say you want to match everything containing the some word say bull but don’t want to match bullshit. Here is a nice way to do that:

+
+

# match all string containing ‘bull’ (bullshit comprised) /bull/

+

match all string containing ‘bull’ except ‘bullshit’

+

/bull([^s]|)∣bulls([h]∣)| bullsh([^i]|)∣bullshi([t]∣)/

+

another way to write it would be

+/bull([^s]|s([h]∣)|sh([^i]|)∣shi([t]∣))/
+
+ +

Let look closer. In the first line the expression is: bull([^s]|$), why does the $ is needed? Because, without it the word bull would be no more matched. This expression means:

+
+

The string finish by bull
or,
contains bull followed by a letter different from s.

+
+

And this is it. I hope it could help you.

+Notice this method is not always the best. For example try to write a regular expression equivalent to the following conditional expression: +
+ # Begin with ‘a’: ^a # End with ‘a’: c$ # Contain ‘b’: .b. # But isn’t ‘axbxc’ if str.match(/^a.b.c / )andnotstr. match( / axbxc/) do_something end +
+ +

A nice solution is:

+
+ /abc| # length 3 a.bc| # length 4 ab.c| a[^x]b[^x]c| # length 5 a…b.c| # length >5 a.b…c/ +
+ +

This solution uses the maximal length of the string not to be matched. There certainly exists many other methods. But the important lesson is it is not straightforward to exclude something of a regular expression.

+
+

It can be proved that any regular set minus a finite set is also regular.

+
+ +
+ + RSS + + + + + + +
+ +
+
+
+
+

Comments

+
+ + + comments powered by Disqus +
+
+
+ Published on 2010-02-15 +
+ +
+ Yann Esposito© +
+
+ Done with + Vim + & + Hakyll +
+
+
+ +
+ + + + + + + + diff --git a/Scratch/en/blog/2010-02-16-All-but-something-regexp--2-/index.html b/Scratch/en/blog/2010-02-16-All-but-something-regexp--2-/index.html new file mode 100644 index 0000000..eae697a --- /dev/null +++ b/Scratch/en/blog/2010-02-16-All-but-something-regexp--2-/index.html @@ -0,0 +1,208 @@ + + + + + + YBlog - Pragmatic Regular Expression Exclude (2) + + + + + + + + + + + + + +
+ + +
+

Pragmatic Regular Expression Exclude (2)

+
+
+
+
+

In my previous post I had given some trick to match all except something. On the same idea, the trick to match the smallest possible string. Say you want to match the string between ‘a’ and ‘b’, for example, you want to match:

+
+a.....a......b..b..a....a....b...
+
+ +

Here are two common errors and a solution:

+
+/a.*b/
+a.....a......b..b..a....a....b...
+
+ +

The first error is to use the evil .*. Because you will match from the first to the last.

+
+/a.*?b/
+a.....a......b..b..a....a....b...
+
+ +

The next natural way, is to change the greediness. But it is not enough as you will match from the first a to the first b. Then a simple constatation is that our matching string shouldn’t contain any a nor b. Which lead to the last elegant solution.

+
+/a[^ab]*b/
+a.....a......b..b..a....a....b...
+
+ +Until now, that was, easy. Now, just pass at the case you need to match not between a and b, but between strings. For example: +
+ +
  • +… +
  • + +
  • + +This is a bit difficult. You need to match +
    + +
  • +[anything not containing ] +
  • +
    +
    + +

    The first method would be to use the same reasoning as in my previous post. Here is a first try:

    +
    + +
  • +([^<]|<[^l]|])* +
  • +
    +
    + +But what about the following string: +
    + +
  • +… +
  • + +That string should not match. This is why if we really want to match it correctly we need to add: +
    + +
  • +([^<]|<[^l]|])*(|<| +
  • + +

    Yes a bit complicated. But what if the string I wanted to match was even longer?

    +

    Here is the algorithm way to handle this easily. You reduce the problem to the first one letter matching:

    +
    +

    # transform a simple randomly choosen character # to an unique ID # (you should verify the identifier is REALLY unique) # beware the unique ID must not contain the # choosen character s/X/was_x/g s/Y/was_y/g

    +

    transform the long string in this simple character

    +s/ +
  • +

    /X/g s/</li>/Y/g

    +

    use the first method

    +

    s/X([^X]*)Y//g

    +

    retransform choosen letter by string

    +s/X/ +
  • +

    /g s/Y/</li>/g

    +

    retransform the choosen character back

    +s/was_x/X/g s/was_y/Y/g
    +
  • + +

    And it works in only 9 lines for any beginning and ending string. This solution should look less I AM THE GREAT REGEXP M45T3R, URAN00B, but is more convenient in my humble opinion. Further more, using this last solution prove you master regexp, because you know it is difficult to manage such problems with only a regexp.

    +
    +

    I know I used an HTML syntax example, but in my real life usage, I needed to match between en: and ::. And sometimes the string could finish with e::.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-02-16 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-02-18-split-a-file-by-keyword/index.html b/Scratch/en/blog/2010-02-18-split-a-file-by-keyword/index.html new file mode 100644 index 0000000..2fdd9af --- /dev/null +++ b/Scratch/en/blog/2010-02-18-split-a-file-by-keyword/index.html @@ -0,0 +1,139 @@ + + + + + + YBlog - split a file by keyword + + + + + + + + + + + + + +
    + + +
    +

    split a file by keyword

    +
    +
    +
    +
    +

    Strangely enough, I didn’t find any built-in tool to split a file by keyword. I made one myself in awk. I put it here mostly for myself. But it could also helps someone else. The following code split a file for each line containing the word UTC.

    +
    + #!/usr/bin/env awk BEGIN{i=0;} /UTC/ { i+=1; FIC=sprintf(“fic.%03d”,i); } {print $0>>FIC} +
    + +

    In my real world example, I wanted one file per day, each line containing UTC being in the following format:

    +
    +Mon Dec  7 10:32:30 UTC 2009
    +
    + +

    I then finished with the following code:

    +
    + #!/usr/bin/env awk BEGIN{i=0;} /UTC/ { date=$1$2$3; if ( date != olddate ) { olddate=date; i+=1; FIC=sprintf(“fic.%03d”,i); } } {print $0>>FIC} +
    + + +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-02-18 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/code/regex_benchmark_ext.rb b/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/code/regex_benchmark_ext.rb new file mode 100644 index 0000000..1770c6a --- /dev/null +++ b/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/code/regex_benchmark_ext.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby +require 'benchmark' +n=80000 +tab=[ '/accounts/user.json', + '/accounts/user.xml', + '/user/titi/blog/toto.json', + '/user/titi/blog/toto.xml' ] + +puts "Get extname" +Benchmark.bm do |x| + x.report("regexp:") { n.times do + str=tab[rand(4)]; + str.match(/[^.]*$/); + ext=$&; + end } + x.report(" split:") { n.times do + str=tab[rand(4)]; + ext=str.split('.')[-1] ; + end } + x.report(" File:") { n.times do + str=tab[rand(4)]; + ext=File.extname(str); + end } +end diff --git a/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/code/regex_benchmark_strip.rb b/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/code/regex_benchmark_strip.rb new file mode 100644 index 0000000..e41a213 --- /dev/null +++ b/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/code/regex_benchmark_strip.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby +require 'benchmark' +n=80000 +tab=[ '/accounts/user.json', + '/accounts/user.xml', + '/user/titi/blog/toto.json', + '/user/titi/blog/toto.xml' ] + +puts "remove extension" +Benchmark.bm do |x| + x.report(" File:") { n.times do + str=tab[rand(4)]; + path=File.expand_path(str,File.basename(str,File.extname(str))); + end } + x.report("chomp:") { n.times do + str=tab[rand(4)]; + ext=File.extname(str); + path=str.chomp(ext); + end } +end diff --git a/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/index.html b/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/index.html new file mode 100644 index 0000000..a39e7cd --- /dev/null +++ b/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/index.html @@ -0,0 +1,165 @@ + + + + + + YBlog - When regexp is not the best solution + + + + + + + + + + + + + +
    + + +
    +

    When regexp is not the best solution

    +
    +
    +
    +
    +

    Regular expression are really useful. Unfortunately, they are not always the best way of doing things. Particularly when transformations you want to make are easy.

    +

    I wanted to know how to get file extension from filename the fastest way possible. There is 3 natural way of doing this:

    +
    +

    # regexp str.match(/[^.]* / ); ext = &

    +

    split

    +

    ext=str.split(‘.’)[-1]

    +

    File module

    +ext=File.extname(str)
    +
    + +

    At first sight I believed that the regexp should be faster than the split because it could be many . in a filename. But in reality, most of time there is only one dot and I realized the split will be faster. But not the fastest way. There is a function dedicated to this work in the File module.

    +

    Here is the Benchmark ruby code:

    +
    +

    #!/usr/bin/env ruby require ‘benchmark’ n=80000 tab=[ ‘/accounts/user.json’, ‘/accounts/user.xml’, ‘/user/titi/blog/toto.json’, ‘/user/titi/blog/toto.xml’ ]

    +puts “Get extname” Benchmark.bm do |x| x.report(“regexp:”) { n.times do str=tab[rand(4)]; str.match(/[^.]* / ); ext = &; end } x.report(" split:“) { n.times do str=tab[rand(4)]; ext=str.split(‘.’)[-1] ; end } x.report(” File:") { n.times do str=tab[rand(4)]; ext=File.extname(str); end } end +
    + +

    And here is the result

    +
    +Get extname
    +            user     system      total        real
    +regexp:  2.550000   0.020000   2.570000 (  2.693407)
    + split:  1.080000   0.050000   1.130000 (  1.190408)
    +  File:  0.640000   0.030000   0.670000 (  0.717748)
    +
    + +

    Conclusion of this benchmark, dedicated function are better than your way of doing stuff (most of time).

    +

    file path without the extension.

    +
    +

    #!/usr/bin/env ruby require ‘benchmark’ n=80000 tab=[ ‘/accounts/user.json’, ‘/accounts/user.xml’, ‘/user/titi/blog/toto.json’, ‘/user/titi/blog/toto.xml’ ]

    +puts “remove extension” Benchmark.bm do |x| x.report(" File:“) { n.times do str=tab[rand(4)]; path=File.expand_path(str,File.basename(str,File.extname(str))); end } x.report(”chomp:") { n.times do str=tab[rand(4)]; ext=File.extname(str); path=str.chomp(ext); end } end +
    + +

    and here is the result:

    +
    +remove extension
    +          user     system      total        real
    + File:  0.970000   0.060000   1.030000 (  1.081398)
    +chomp:  0.820000   0.040000   0.860000 (  0.947432)
    +
    + +

    Conclusion of the second benchmark. One simple function is better than three dedicated functions. No surprise, but it is good to know.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-02-23 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-03-22-Git-Tips/index.html b/Scratch/en/blog/2010-03-22-Git-Tips/index.html new file mode 100644 index 0000000..caa3822 --- /dev/null +++ b/Scratch/en/blog/2010-03-22-Git-Tips/index.html @@ -0,0 +1,154 @@ + + + + + + YBlog - Git Tips + + + + + + + + + + + + + +
    + + +
    +

    Git Tips

    +
    +
    +
    +
    +

    clone from github behind an evil firewall

    +

    Standard:

    +
    + git clone git@github.com:yogsototh/project.git +
    + +

    Using HTTPS port:

    +
    + git clone git+ssh://git@github.com:443/yogsototh/project.git +
    + +

    clone all branches

    +

    git clone can only fetch the master branch.

    +

    If you don’t have much branches, you can simply use clone your project and then use the following command:

    +
    + git branch –track local_branch remote_branch +
    + +for example: +
    + $ git clone git@github:yogsototh/example.git $ git branch master * $ git branch -a master * remotes/origin/HEAD -> origin/master remotes/origin/experimental $ git branch –track experimental remotes/origin/experimental $ git branch master * experimental +
    + +

    If you have many branches it can be useful to use the following script/long command line.

    +
    +

    # first clone your project $ git clone git@github.com:yogsototh/project.git

    +

    copy all branches

    +$ zsh $ cd project $ for br in (gitbr − a); docasebr in remotes/*) print br; case{br:t} in master|HEAD) continue ;; *) git branch –track br: tbr ;; esac ;; esac done
    +
    + + +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-03-22 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-03-23-Encapsulate-git/code/eng b/Scratch/en/blog/2010-03-23-Encapsulate-git/code/eng new file mode 100644 index 0000000..8dc7396 --- /dev/null +++ b/Scratch/en/blog/2010-03-23-Encapsulate-git/code/eng @@ -0,0 +1,102 @@ +#!/usr/bin/env ruby +# encoding: utf-8 + +# architecture +# +# master <-> dev +# master -> client +# clien -> clientA | clientB +# +# merge using two of these branches should be +# restricted to these rules +# merge to one of these branch and an unknown one should +# raise a warning, and may the option to add this new branch +# to the hierarchy + +$architecture={ + :master => [ :dev, :client ], + :dev => [ :master ], + :client => [ :clientA, :clientB ] } + +def get_current_branch() + (`git branch --no-color | awk '$1 == "*" {print $2}'`).chop.intern +end + +if ARGV.length == 0 + puts %{usage: $0:t [git_command or local_command] + +local commands: + allmerges: merge from top to down} + exit 0 +end + +require 'set' +$known_branches=Set.new +$architecture.each do |k,v| + $known_branches.add(k) + v.each { |b| $known_branches.add(b) } +end + +def rec_merge(branch) + if $architecture[branch].nil? + return + end + $architecture[branch].each do |b| + if $flag.has_key?(b.to_s + branch.to_s) + next + end + flagname=branch.to_s + b.to_s + if $flag.has_key?(flagname) + next + end + if system %{eng checkout #{b}} + if get_current_branch != b + puts "Can't checkout to #{b}" + exit 2 + end + if system %{eng merge #{branch}} + $flag[flagname]=true + rec_merge(b) + else + exit 1 + end + else + exit 1 + end + end +end + +def do_all_merges + puts 'Will merge from father to sons' + current_branch=get_current_branch + $flag={} + rec_merge(:master) + system %{git co #{current_branch}} +end + +def do_merge + current_branch=get_current_branch + src_branch=ARGV[1].intern + puts %{do_merge: #{src_branch} => #{current_branch}} + if $known_branches.include?(current_branch) + if $known_branches.include?(src_branch) + if $architecture.has_key?(src_branch) and + $architecture[src_branch].include?(current_branch) + system %{git merge #{src_branch}} + else + puts %{Forbidden merge: #{src_branch} => #{current_branch}} + end + else + puts %{Warning! #{src_branch} not mentionned in rb configuration} + sleep 2 + system %{git merge #{src_branch}} + puts %{Warning! #{src_branch} not mentionned in rb configuration} + end + end +end + +case ARGV[0] + when 'allmerges' then do_all_merges + when 'merge' then do_merge + else system %{git #{ARGV.join(' ')}} +end diff --git a/Scratch/en/blog/2010-03-23-Encapsulate-git/index.html b/Scratch/en/blog/2010-03-23-Encapsulate-git/index.html new file mode 100644 index 0000000..15f1bc5 --- /dev/null +++ b/Scratch/en/blog/2010-03-23-Encapsulate-git/index.html @@ -0,0 +1,176 @@ + + + + + + YBlog - Encapsulate git + + + + + + + + + + + + + +
    + + +
    +

    Encapsulate git

    +
    +
    +
    +
    +
    +Here is a solution to maintain divergent branches in git. Because it is easy to merge by mistake. I give a script that encapsulate git in order to forbid some merge and warn you some merge should be dangerous. +
    + +

    how to protect against your own dumb

    +

    I work on a project in which some of my git branches should remain divergent. And divergences should grow.

    +

    I also use some branch to contain what is common between projects.

    +

    Say I have some branches:

    +

    master: common to all branches dev: branch devoted to unstable development client: branch with features for all client but not general enough for master clientA: project adapted for client A clientB: project adapted for client B

    +

    Here how I want to work:

    +

    Dynamic branching

    +

    And more precisely the branch hierarchy:

    +

    Branch hierarchy

    +

    An arrow from A to B means, you can merge A in B. If there is no arrow from A to B that means it is forbidden to merge A in B. Here is the corresponding rubycode:

    +
    + $architecture={ :master => [ :dev, :client ], :dev => [ :master ], :client => [ :clientA, :clientB ] } +
    + +

    Having a :master => [ :dev, :client ] means you can merge master branch into dev and client.

    +

    If by mistake I make a git checkout master && git merge clientA, I made a mistake. This is why I made a script which encapsulate the git behaviour to dodge this kind of mistake.

    +

    But this script do far more than that. It also merge from top to down. The action allmerges will do:

    +
    + git co dev && git merge master git co client && git merge master git co clientA && git merge client git co clientB && git merge client +
    + +

    That means, I can update all branches. The algorithm will not make loop even if there is a cycle in the branch hierarchy.

    +

    Here it is:

    +
    +

    #!/usr/bin/env ruby # encoding: utf-8

    +

    architecture

    +

    +

    master <-> dev

    +

    master -> client

    +

    clien -> clientA | clientB

    +

    +

    merge using two of these branches should be

    +

    restricted to these rules

    +

    merge to one of these branch and an unknown one should

    +

    raise a warning, and may the option to add this new branch

    +

    to the hierarchy

    +

    $architecture={ :master => [ :dev, :client ], :dev => [ :master ], :client => [ :clientA, :clientB ] }

    +

    def get_current_branch() (git branch --no-color | awk '$1 == "*" {print $2}').chop.intern end

    +

    if ARGV.length == 0 puts %{usage: $0:t [git_command or local_command]

    +

    local commands: allmerges: merge from top to down} exit 0 end

    +

    require ‘set’ knownbranches = Set. newarchitecture.each do |k,v| $known_branches.add(k) v.each { |b| $known_branches.add(b) } end

    +

    def rec_merge(branch) if architecture[branch]. nil? returnendarchitecture[branch].each do |b| if flag. haskey? (b. tos + branch. tos)nextendflagname = branch. tos + b. tosifflag.has_key?(flagname) next end if system %{eng checkout #{b}} if get_current_branch != b puts “Can’t checkout to #{b}” exit 2 end if system %{eng merge #{branch}} $flag[flagname]=true rec_merge(b) else exit 1 end else exit 1 end end end

    +

    def do_all_merges puts ‘Will merge from father to sons’ current_branch=get_current_branch $flag={} rec_merge(:master) system %{git co #{current_branch}} end

    +

    def do_merge current_branch=get_current_branch src_branch=ARGV[1].intern puts %{do_merge: #{src_branch} => #{current_branch}} if knownbranches. include? (currentbranch)ifknown_branches.include?(src_branch) if architecture. haskey? (srcbranch)andarchitecture[src_branch].include?(current_branch) system %{git merge #{src_branch}} else puts %{Forbidden merge: #{src_branch} => #{current_branch}} end else puts %{Warning! #{src_branch} not mentionned in rb configuration} sleep 2 system %{git merge #{src_branch}} puts %{Warning! #{src_branch} not mentionned in rb configuration} end end end

    +case ARGV[0] when ‘allmerges’ then do_all_merges when ‘merge’ then do_merge else system %{git #{ARGV.join(‘’)}} end
    +
    + +

    All you need to do to make it work is simply to copy eng in a directory contained in your PATH.

    +

    Of course try to use as few as possible cherry-pick and rebase. This script was intended to work with workflow using pull and merge.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-03-23 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-05-17-at-least-this-blog-revive/index.html b/Scratch/en/blog/2010-05-17-at-least-this-blog-revive/index.html new file mode 100644 index 0000000..a0ffb39 --- /dev/null +++ b/Scratch/en/blog/2010-05-17-at-least-this-blog-revive/index.html @@ -0,0 +1,144 @@ + + + + + + YBlog - I live again! + + + + + + + + + + + + + +
    + + +
    +

    I live again!

    +
    +
    +
    +
    +

    Hi all!

    +
    +

    The more you wait to do something, the more difficult it is to start doing it. {: cite=“http://www.madore.org/~david/weblog/2010-05.html#d.2010-05-12.1752” }

    +
    +

    I had to write another post for this blog. I had added many article idea in my todolist. But, I made many other things, and I’ve always said (until now), I’ll do this later. What changed my mind is the haunt of this simple remark about how to be productive in programming. > Stop write TODO in your code and make it now!
    > You’ll be surprised by the results.

    +

    In short: > Just do it! ou Juste fait le comme auraient dit les nuls.

    +

    Finally I’ll certainly write blog post more often for a short period of time.

    +

    What did I do?

    +

    I finished some web services/application for gridpocket(c).

    +

    I also finished to update my blog engine to nanoc3. The difficult part was to handle nicely multiple languages. But I should detail why in a future post.

    +

    I also have a real life. I enjoyed some vacancies with my family.

    +

    I work with Luc on a simple ruby REST/JSON/API oriented framework. It works fairly well, with really few bug until now. We planify to make a simple todolist tutorial. May be in two to three blog posts. This framework is not public for now. It will certainly be after we’ll create some simple web service with it and made a nice website for it.

    +

    Then what I plan to do from now:

    +
      +
    • finish to make a public web service (I believe it can be popular)
    • +
    • finish to write the associated iPhone application for it
    • +
    • finish to publish our private framework to make web services
    • +
    • publish some articles about this blog (at least 3)
    • +
    • provide the sources of this website on github
    • +
    +

    There is some random in some of these achivement mostly because they don’t depend totally on me.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-05-17 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-05-19-How-to-cut-HTML-and-repair-it/code/repair_xml.rb b/Scratch/en/blog/2010-05-19-How-to-cut-HTML-and-repair-it/code/repair_xml.rb new file mode 100644 index 0000000..bc18f6c --- /dev/null +++ b/Scratch/en/blog/2010-05-19-How-to-cut-HTML-and-repair-it/code/repair_xml.rb @@ -0,0 +1,24 @@ +# repair cutted XML code by closing the tags +# work even if the XML is cut into a tag. +# example: +# transform '
    toto

    hello ]*$/m,'') + depth-=1 + depth.downto(0).each { |x| res<<= %{} } + res +end diff --git a/Scratch/en/blog/2010-05-19-How-to-cut-HTML-and-repair-it/index.html b/Scratch/en/blog/2010-05-19-How-to-cut-HTML-and-repair-it/index.html new file mode 100644 index 0000000..363b020 --- /dev/null +++ b/Scratch/en/blog/2010-05-19-How-to-cut-HTML-and-repair-it/index.html @@ -0,0 +1,189 @@ + + + + + + YBlog - How to repair a cutted XML? + + + + + + + + + + + + + +

    + + +
    +

    How to repair a cutted XML?

    +
    +
    +
    +
    +

    For my main page, you can see, a list of my latest blog entry. And you have the first part of each article. To accomplish that, I needed to include the begining of the entry and to cut it somewhere. But now, I had to repair this cutted HTML.

    +

    Here is an example:

    +
    <div class="corps">
    +    <div class="intro">
    +        <p>Introduction</p>
    +    </div>
    +    <p>The first paragraph</p>
    +    <img src="/img/img.png" alt="an image"/>
    +    <p>Another long paragraph</p>
    +</div>
    +

    After the cut, I obtain:

    +
    <div class="corps">
    +    <div class="intro">
    +        <p>Introduction</p>
    +    </div>
    +    <p>The first paragraph</p>
    +    <img src="/img/im
    +

    Argh! In the middle of an <img> tag.

    +

    In fact, it is not as difficult as it should sound first. The secret is, you don’t need to keep the complete tree structure to repair it, but only the list of not closed parents.

    +

    Given with our example, when we are after the first paragraph. we only have to close the div for class corps and the XML is repaired. Of course, when you cut inside a tag, you sould go back, as if you where just before it. Delete this tag and all is ok.

    +

    Then, all you have to do, is not remember all the XML tree, but only the heap containing your parents. Suppose we treat the complete first example, the stack will pass through the following state, in order:

    +
    []           
    +[div]           <div class="corps">
    +[div, div]          <div class="intro">
    +[div, div, p]           <p>
    +                            Introduction
    +[div, div]              </p>
    +[div]               </div>
    +[div, p]            <p>
    +                        The first paragraph
    +[div]               </p>
    +[div]               <img src="/img/img.png" alt="an image"/>
    +[div, p]            <p>
    +                        Another long paragraph
    +[div]               </p>
    +[]              </div>
    +

    The algorihm, is then really simple: ~~~~~~ {.html} let res be the XML as a string ; read res and each time you encouter a tag: if it is an opening one: push it to the stack else if it is a closing one: pop the stack.

    +

    remove any malformed/cutted tag in the end of res for each tag in the stack, pop it, and write: res = res + closed tag

    +

    return res ~~~~~~

    +

    And res contain the repaired XML.

    +

    Finally, this is the code in ruby I use. The xml variable contain the cutted XML.

    +
    # repair cutted XML code by closing the tags
    +# work even if the XML is cut into a tag.
    +# example:
    +#    transform '<div> <span> toto </span> <p> hello <a href="http://tur'
    +#    into      '<div> <span> toto </span> <p> hello </p></div>'
    +def repair_xml( xml )
    +    parents=[]
    +    depth=0
    +    xml.scan( %r{<(/?)(\w*)[^>]*(/?)>} ).each do |m|
    +        if m[2] == "/"
    +            next
    +        end
    +        if m[0] == "" 
    +            parents[depth]=m[1]
    +            depth+=1
    +        else
    +            depth-=1
    +        end
    +    end
    +    res=xml.sub(/<[^>]*$/m,'')
    +    depth-=1
    +    depth.downto(0).each { |x| res<<= %{</#{parents[x]}>} }
    +    res
    +end
    +

    I don’t know if the code can help you, but the raisonning should definitively be known.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-05-19 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/code/The_destination_tree.png b/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/code/The_destination_tree.png new file mode 100644 index 0000000..16c8345 Binary files /dev/null and b/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/code/The_destination_tree.png differ diff --git a/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/code/The_source_tree.png b/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/code/The_source_tree.png new file mode 100644 index 0000000..eeb5b55 Binary files /dev/null and b/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/code/The_source_tree.png differ diff --git a/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/index.html b/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/index.html new file mode 100644 index 0000000..f1d8588 --- /dev/null +++ b/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/index.html @@ -0,0 +1,313 @@ + + + + + + YBlog - Trees; Pragmatism and Formalism + + + + + + + + + + + + + +
    + + +
    +

    Trees; Pragmatism and Formalism

    +
    +
    +
    +
    +
    + +

    tl;dr: :

    +
      +
    • I tried to program a simple filter
    • +
    • Was blocked 2 days
    • +
    • Then stopped working like an engineer monkey
    • +
    • Used a pen and a sheet of paper
    • +
    • Made some math.
    • +
    • Crushed the problem in 10 minutes
    • +
    • Conclusion: The pragmatism shouldn’t mean “never use theory”.
    • +
    +
    + +

    Abstract (longer than tl;dr: )

    +

    For my job, I needed to resolve a problem. It first seems not too hard. Then I started working directly on my program. I entered in the infernal: try & repair loop. Each step was like:

    +
    +

    – Just this thing to repair and that should be done.
    – OK, now that should just work.
    – Yeah!!!
    – Oops! I forgotten that…
    repeat until death

    +
    +

    After two days of this Sisyphus work, I finally just stopped to rethink the problem. I took a pen, a sheet of paper. I simplified the problem, reminded what I learned during my Ph.D. about trees. Finally, the problem was crushed in less than 20 minutes.

    +

    I believe the important lesson is to remember that the most efficient methodology to resolve this pragmatic problem was the theoretical one. And therefore, argues opposing science, theory to pragmatism and efficiency are fallacies.

    +
    +

    First: my experience

    +

    Apparently 90% of programmer are unable to program a binary search without bug. The algorithm is well known and easy to understand. However it is difficult to program it without any flaw. I participated to this contest. And you can see the results here1. I had to face a problem of the same kind at my job. The problem was simple to the start. Simply transform an xml from one format to another.

    +

    The source xml was in the following general format:

    +
    <rubrique>
    +    <contenu>
    +        <tag1>value1</tag1>
    +        <tag2>value2</tag2>
    +        ...
    +    </contenu>
    +    <enfant>
    +        <rubrique>
    +            ...
    +        </rubrique>
    +        ...
    +        <rubrique>
    +            ...
    +        </rubrique>
    +    </enfant>
    +</menu>
    +

    and the destination format was in the following general format:

    +
    <item name="Menu0">
    +    <value>
    +        <item name="menu">
    +            <value>
    +                <item name="tag1">
    +                    <value>value1</value>
    +                </item>
    +                <item name="tag2">
    +                    <value>value2</value>
    +                </item>
    +                ...
    +                <item name="menu">
    +                    <value>
    +                        ...
    +                    </value>
    +                    <value>
    +                        ...
    +                    </value>
    +                </item>
    +            </value>
    +        </item>
    +    </value>
    +</item>
    +

    At first sight I believed it will be easy. I was so certain it will be easy that I fixed to myself the following rules:

    +
      +
    1. do not use xslt
    2. +
    3. avoid the use of an xml parser
    4. +
    5. resolve the problem using a simple perl script[^2]
    6. +
    +

    You can try if you want. If you attack the problem directly opening an editor, I assure you, it will certainly be not so simple. I can tell that, because it’s what I’ve done. And I must say I lost almost a complete day at work trying to resolve this. There was also, many small problems around that make me lose more than two days for this problem.

    +

    Why after two days did I was unable to resolve this problem which seems so simple?

    +

    What was my behaviour (workflow)?

    +
      +
    1. Think
    2. +
    3. Write the program
    4. +
    5. Try the program
    6. +
    7. Verify the result
    8. +
    9. Found a bug
    10. +
    11. Resolve the bug
    12. +
    13. Go to step 3.
    14. +
    +

    This was a standard workflow for computer engineer. The flaw came from the first step. I thought about how to resolve the problem but with the eyes of a pragmatic engineer. I was saying:

    +
    +

    That should be a simple perl search and replace program.
    Let’s begin to write code

    +
    +

    This is the second sentence that was plainly wrong. I started in the wrong direction. And the workflow did not work from this entry point.

    +

    Think

    +

    After some times, I just stopped to work. Tell myself “it is enough, now, I must finish it!”. I took a sheet of paper, a pen and began to draw some trees.

    +

    I began by make by removing most of the verbosity. I first renamed <item name="Menu"> by simpler name M for example. I obtained something like:

    +

    subgraph cluster_x { node [label=“C”] C_x ; node [label=“E”] E_x ; node [label=“a1”] tag1_x ; node [label=“a2”] tag2_x ; node [label=“R”, color=“#333333”, fillcolor=“#333333”, fontcolor=“white”] R_x ; R_x -> C_x; C_x -> tag1_x ; C_x -> tag2_x ; R_x -> E_x ; } subgraph cluster_y { node [label=“C”] C_y ; node [label=“E”] E_y ; node [label=“a1”] tag1_y ; node [label=“a2”] tag2_y ; node [label=“R”, color=“#333333”, fillcolor=“#333333”, fontcolor=“white”] R_y ; R_y -> C_y; C_y -> tag1_y ; C_y -> tag2_y ; R_y -> E_y ; } subgraph cluster_z { node [label=“C”] C_z ; node [label=“E”] E_z ; node [label=“a1”] tag1_z ; node [label=“a2”] tag2_z ; node [label=“R”, color=“#333333”, style=“filled”, fillcolor=“#333333”, fontcolor=“white”] R_z ; R_z -> C_z; C_z -> tag1_z ; C_z -> tag2_z ; R_z -> E_z ; } E_x -> R_y ; E_x -> R_z ;

    +

    +

    and

    +

    subgraph cluster_x { node [label=“M”] E_x ; node [label=“a1”] tag1_x ; node [label=“V”] value_tag1_x ; node [label=“a2”] tag2_x ; node [label=“V”] value_tag2_x ; node [label=“V”, color=“#333333”, fillcolor=“#333333”, fontcolor=“white”] R_x ; R_x -> value_tag1_x -> tag1_x ; R_x -> value_tag2_x -> tag2_x ; R_x -> E_x ; } subgraph cluster_y { node [label=“M”] E_y ; node [label=“a1”] tag1_y ; node [label=“V”] value_tag1_y ; node [label=“a2”] tag2_y ; node [label=“V”] value_tag2_y ; node [label=“V”, color=“#333333”, fillcolor=“#333333”, fontcolor=“white”] R_y ; R_y -> value_tag1_y -> tag1_y ; R_y -> value_tag2_y -> tag2_y ; R_y -> E_y ; } subgraph cluster_z { node [label=“M”] E_z ; node [label=“a1”] tag1_z ; node [label=“V”] value_tag1_z ; node [label=“a2”] tag2_z ; node [label=“V”] value_tag2_z ; node [label=“V”, color=“#333333”, fillcolor=“#333333”, fontcolor=“white”] R_z ; R_z -> value_tag1_z -> tag1_z ; R_z -> value_tag2_z -> tag2_z ; R_z -> E_z ; } E_x -> R_y ; E_x -> R_z ;

    +

    +

    Then I made myself the following reflexion:

    +

    Considering Tree Edit Distance, each unitary transformation of tree correspond to a simple search and replace on my xml source2. We consider three atomic transformations on trees:

    +
      +
    • substitution: renaming a node
    • +
    • insertion: adding a node
    • +
    • deletion: remove a node
    • +
    +

    One of the particularity of atomic transformations on trees, is ; if you remove a node, all children of this node, became children of its father.

    +

    An example:

    +
    +r - x - a
    +  \   \
    +   \    b
    +    y - c   
    +
    + +

    If you delete the x node, you obtain

    +
    +    a
    +  /
    +r - b
    +  \
    +    y - c   
    +
    + +

    And look at what it implies when you write it in xml:

    +
    <r>
    +  <x>
    +    <a>value for a</a>
    +    <b>value for b</b>
    +  </x>
    +  <y>
    +    <c>value for c</c>
    +  </y>
    +</r>
    +

    Then deleting all x nodes is equivalent to pass the xml via the following search and replace script:

    +
    s/<\/?x>//g
    +

    Therefore, if there exists a one state deterministic transducer which transform my trees ; I can transform the xml from one format to another with just a simple list of search and replace directives.

    +

    Solution

    +

    Transform this tree:

    +
    +R - C - tag1
    +  \   \
    +   \    tag2
    +    E -- R - C - tag1
    +      \   \    \
    +       \   \     tag2
    +        \    E ...
    +         R - C - tag1 
    +           \    \
    +            \     tag2
    +             E ...
    +
    + +

    to this tree:

    +
    +                tag1
    +              /
    +M - V - M - V - tag2      tag1
    +              \         / 
    +                M --- V - tag2
    +                  \     \ 
    +                   \      M
    +                    \     tag1
    +                     \  / 
    +                      V - tag2
    +                        \ 
    +                          M
    +
    + +

    can be done using the following one state deterministic tree transducer:

    +
    +

    C -> ε
    E -> M
    R -> V

    +
    +

    Wich can be traduced by the following simple search and replace directives:

    +
    s/C//g
    +s/E/M/g
    +s/R/V/g
    +

    Once adapted to xml it becomes:

    +
    s%</?contenu>%%g
    +s%<enfant>%<item name="menu">%g
    +s%</enfant>%</item>%g
    +s%<rubrique>%<value>%g
    +s%</rubrique>%</value>%g
    +

    That is all.

    +

    Conclusion

    +

    It should seems a bit paradoxal, but sometimes the most efficient approach to a pragmatic problem is to use the theoretical methodology.

    +
    +
    +
      +
    1. Hopefully I am in the 10% who had given a bug free implementation.

    2. +
    3. I did a program which generate automatically the weight in a matrix of each edit distance from data.

    4. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-05-24 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-06-14-multi-language-choices/index.html b/Scratch/en/blog/2010-06-14-multi-language-choices/index.html new file mode 100644 index 0000000..87ac214 --- /dev/null +++ b/Scratch/en/blog/2010-06-14-multi-language-choices/index.html @@ -0,0 +1,152 @@ + + + + + + YBlog - multi language choices + + + + + + + + + + + + + +
    + + +
    +

    multi language choices

    +
    +
    +
    +
    +

    I translate most of my blog entries in French and English. Most people advice me to have one file per language. Generally it ends with:

    +
    +Bonjour, 
    +
    +voici un exemple de texte en français.
    +[image](url)
    +
    + +
    +Hello, 
    +
    +here is an example of english text.
    +[image](url)
    +
    + +

    This way of handling translations force you to write completely an article in one language, copy it, and translate it.

    +

    However, most of time, there are common parts like images, source code, etc… When I want to correct some mistake on these parts, I have to make twice the work. With sometimes adding another mistake in only one language.

    +

    This is why I preferred to handle it differently. I use tags on a single file. Finally my files looks like:

    +
    + fr:   Bonjour, 
    + en:   Hello, 
    +
    + en:   here is an example of english text.
    + fr:   voici un exemple de texte en français.
    +[image](url)
    +
    + +

    As I edit my files with vim, it is really easy to add fr: or en: at some line’s beginning using the useful C-v. However nanoc was conceived to be used for one language only. Or to be used with the first method. I tried to adapt nanoc to my usage. But after a while, I found it easier to pre-filter the nanoc work by a simple script. My script transform my file into two new files. And all work like a charm.

    +

    You can get my blog code source (without most of articles) at github.com/yogsototh/Scratch.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-06-14 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-06-15-Get-my-blog-engine/index.html b/Scratch/en/blog/2010-06-15-Get-my-blog-engine/index.html new file mode 100644 index 0000000..a1e289f --- /dev/null +++ b/Scratch/en/blog/2010-06-15-Get-my-blog-engine/index.html @@ -0,0 +1,185 @@ + + + + + + YBlog - Get my blog engine + + + + + + + + + + + + + +
    + + +
    +

    Get my blog engine

    +
    +
    +
    +
    +

    I published a light version of my blog engine based on nanoc yesterday night. By light, I mean a lighter, more portable CSS (without round border). You can get it on github.com.

    +

    What this system provide?

    +
      +
    • All nanoc advantages,
    • +
    • Easy multi-language handling,
    • +
    • Syntax Coloration for most languages,
    • +
    • intenseDebate comments integration (asynchronous) ;
    • +
    • Portable with and without javascript, XHTML Strict 1.0 / CSS3,
    • +
    • Write in markdown format (no HTML editing needed),
    • +
    • Typographic ameliorations (no ‘:’ starting a line in French for example),
    • +
    • Graphviz graph generation integration.
    • +
    +
    +

    Main Documentation Page

    +

    Use It NOW!

    +

    Once installed (follow the README.md instructions).

    +
    $ cd /root/of/nanoc3_blog
    +$ ./task/new_blog_entry Title of the blog
    +$ vi latest.md
    +$ ./task/recompile
    +

    Now your website reside into the output directory.

    +
    +

    Documentation

    +

    Useful things to know

    +

    Multi-language

    +

    All files in multi are processed and copied in the content directory. For each file in multi, each line starting by ‘fr:’ are copied (without the fr: into the content/html/fr/ tree, but not into the content/html/en tree. File not starting by fr: or en: are copied in each destinations.

    +

    If you want to add another language, you’ll have to modify tasks/config, and config.yaml, create a content/html/xx where xx is the language code.

    +

    Edition & Rendering

    +

    additional keywords

    +

    You can separate multi content div using the: n``ewcorps directive (see examples).

    +

    You can create div using b``egindiv(classname), e``nddiv. (See some existing blog entries for example). Use the class intro for the abstract part.

    +

    You can create nice description table using <``desc> (See source code for example).

    +

    Typography

    +

    In French all ‘:’, ‘;’, ‘!’ and ‘?’ are preceded automatically by &nbsp. This enable not to have a line starting by a single special character.

    +

    You can use small caps using <sc> tags.

    +
      +
    • (c``) is replaced by (c).
    • +
    • (r``) is replaced by (r).
    • +
    • <``- is replaced by <-.
    • +
    • -``> is replaced by ->.
    • +
    +

    source code

    +

    To write source code you should use the following format:

    +

    ~~~~~~ {.html} ~~~~~~ {.ruby} The code ~~~~~~

    +

    The file attribute is not required.

    +

    blog

    +

    If you want to make really long blog post, you can separate them into many files. To accomplish that, you simply have to make your files like:

    +
    +multi/blog/2010-06-01-the-title.md
    +multi/blog/2010-06-01-the-title/second_part.md
    +multi/blog/2010-06-01-the-title/third_part.md
    +
    + +

    mobileme

    +

    All files are intended to be generated into the output/Scratch directory. This was made like that to work nicely with iWeb organisation of websites.

    + +

    The order of post is done using the menupriority meta-data in the header of the files.

    +

    You can hide some file from the menu by setting: isHidden: true in the header.

    +

    Details

    +

    To know more about this blog engine, you should look at nanoc project.

    +

    Then look at the files inside your project:

    +

    README.md : readme for the project (used by github) :: latest.md : symbolic link to the last blog entry :: multi/ : Directory containing multi-language articles :: tasks/ : scripts for website live :: config.yaml : global configuration file :: Rules : generation rules :: content/ : content files processed by nanoc :: layouts/ : erb templates :: lib/ : ruby libraries used to process files :: output/ : website :: Rakefile : not mandatory for this blog ::

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-06-15 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_hidden.html b/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_hidden.html new file mode 100644 index 0000000..946d873 --- /dev/null +++ b/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_hidden.html @@ -0,0 +1,20 @@ + + + + + + + + + Hide to analytics + + +
    + + diff --git a/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_visible.html b/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_visible.html new file mode 100644 index 0000000..0f79658 --- /dev/null +++ b/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_visible.html @@ -0,0 +1,20 @@ + + + + + + + + + Hide to analytics + + +
    + + diff --git a/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/index.html b/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/index.html new file mode 100644 index 0000000..b4e9aca --- /dev/null +++ b/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/index.html @@ -0,0 +1,175 @@ + + + + + + YBlog - Hide Yourself to your Analytics + + + + + + + + + + + + + +
    + + +
    +

    Hide Yourself to your Analytics

    +
    +
    +
    +
    +

    This is a way not to count your own visits to your blog. First you should look on how I handle analytics. All analytics are handled in one javascript file, this make things really convenient.

    +

    Then you need to know my method use the jquery-cookie.

    +

    I check if the key admin is not set in the cookie before adding the visit.

    +
        var admin = $.cookie('admin');
    +    if (! admin) {
    +        // put your analytics code here
    +    } else {
    +        console.log("[WARNING] you're HIDDEN to analytics");
    +    }
    +

    then create two html files. One to hide:

    +
    <?xml version="1.0" encoding="utf-8"?>
    +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    +        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    +<html xmlns="http://www.w3.org/1999/xhtml" lang="fr" xml:lang="fr">
    +    <head>
    +        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    +        <script type="text/javascript" src="jquery.js"></script>
    +        <script type="text/javascript" src="jquery.cookie.js"></script>
    +        <script>
    +            $(document).ready(function(){
    +                $.cookie('admin',1);
    +                $('#info').html('Analytics can no more see you.')
    +            });
    +        </script>
    +        <title>Hide to analytics</title>
    +    </head>
    +    <body>
    +        <div id="info"></div> 
    +    </body>
    +</html>
    +

    the other to be visible again (it can be useful):

    +
    <?xml version="1.0" encoding="utf-8"?>
    +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    +        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    +<html xmlns="http://www.w3.org/1999/xhtml" lang="fr" xml:lang="fr">
    +    <head>
    +        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    +        <script type="text/javascript" src="jquery.js"></script>
    +        <script type="text/javascript" src="jquery.cookie.js"></script>
    +        <script>
    +            $(document).ready(function(){
    +                $.cookie('admin',null);
    +                $('#info').html('Analytics can see you.')
    +            });
    +        </script>
    +        <title>Hide to analytics</title>
    +    </head>
    +    <body>
    +        <div id="info"></div> 
    +    </body>
    +</html>
    +

    Now accessing these files with you browser you can hide or appear in your statistics. You just have to think to access these file from all you browser.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-06-17 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-06-17-track-events-with-google-analytics/code/yga.js b/Scratch/en/blog/2010-06-17-track-events-with-google-analytics/code/yga.js new file mode 100644 index 0000000..99f4a46 --- /dev/null +++ b/Scratch/en/blog/2010-06-17-track-events-with-google-analytics/code/yga.js @@ -0,0 +1,45 @@ +$(document).ready( function() { + // add an event to all link for google analytics + $('a').click(function () { + // tell analytics to save event + try { + var identifier=$(this).attr('id') ; + var href=$(this).attr('href') + var label=""; + if ( typeof( identifier ) != 'undefined' ) { + label=label+'[id]:'+identifier + category='JSLink' + } + if ( typeof( href ) != 'undefined' ) { + label=label+' [href]:'+href + if ( href[0] == '#' ) { + category='Anchor'; + } else { + category='Link'; + } + } + _gaq.push(['_trackEvent', category, 'clicked', label]); + // console.log('[tracked]: ' + category + ' ; clicked ; ' + label ); + } + catch (err) { + console.log(err); + } + + // pause to allow google script to run + var date = new Date(); + var curDate = null; + do { + curDate = new Date(); + } while(curDate-date < 300); + }); +}); + +var _gaq = _gaq || []; +_gaq.push(['_setAccount', 'UA-XXXXXXXX-1']); +_gaq.push(['_trackPageview']); + +(function() { + var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; + ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; + var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); + })(); diff --git a/Scratch/en/blog/2010-06-17-track-events-with-google-analytics/index.html b/Scratch/en/blog/2010-06-17-track-events-with-google-analytics/index.html new file mode 100644 index 0000000..f214a8f --- /dev/null +++ b/Scratch/en/blog/2010-06-17-track-events-with-google-analytics/index.html @@ -0,0 +1,177 @@ + + + + + + YBlog - Track Events with Google Analytics + + + + + + + + + + + + + +
    + + +
    +

    Track Events with Google Analytics

    +
    +
    +
    +
    +

    Here is how to track all clicks on your website using google analytics asynchronously.

    +

    First in your html you need to use jQuery and a javscript file I named yga.js:

    +
        <script type="text/javascript" src="jquery.js"></script>
    +    <script type="text/javascript" src="yga.js"></script>
    +

    And here is the yga.js file:

    +
    $(document).ready( function() {
    +    // add an event to all link for google analytics
    +    $('a').click(function () {
    +        // tell analytics to save event
    +        try {
    +            var identifier=$(this).attr('id') ;
    +            var href=$(this).attr('href')
    +            var label="";
    +            if ( typeof( identifier ) != 'undefined' ) {
    +                label=label+'[id]:'+identifier
    +                category='JSLink'
    +            }
    +            if ( typeof( href ) != 'undefined' ) {
    +                label=label+' [href]:'+href
    +                if ( href[0] == '#' ) {
    +                    category='Anchor';
    +                } else {
    +                    category='Link';
    +                }
    +            }
    +            _gaq.push(['_trackEvent', category, 'clicked', label]);
    +            // console.log('[tracked]: ' + category + ' ; clicked ; ' + label );
    +        }
    +        catch (err) {
    +            console.log(err);
    +        }
    +
    +        // pause to allow google script to run
    +        var date = new Date();
    +        var curDate = null;
    +        do {
    +            curDate = new Date();
    +        } while(curDate-date < 300);
    +    });
    +});
    +
    +var _gaq = _gaq || [];
    +_gaq.push(['_setAccount', 'UA-XXXXXXXX-1']);
    +_gaq.push(['_trackPageview']);
    +
    +(function() {
    + var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    + ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    + var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
    + })();
    +

    Replace the: UA-XXXXXXXX-1 by your google analytics code and you’re done.

    +

    To see what occurs, simply go in Content and Event Tracking as shown in the following screenshot:

    +

    +

    Happy tracking!

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-06-17 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-06-19-jQuery-popup-the-easy-way/code/essai.js b/Scratch/en/blog/2010-06-19-jQuery-popup-the-easy-way/code/essai.js new file mode 100644 index 0000000..d0c8361 --- /dev/null +++ b/Scratch/en/blog/2010-06-19-jQuery-popup-the-easy-way/code/essai.js @@ -0,0 +1,22 @@ +// --- code popup --- +function openPopup() { + $(this).clone(false).appendTo($("#_code")); + $("#_code").show(); +} + +function closePopup() { + $("#_code").html(""); + $("#_code").hide(); +} + +function initCode() { + $(".code").click(openPopup); + $(".code").css({cursor: "pointer"}); + $('body').append('
    '); + $('#_code').css( { 'text-align': "justify", position: "fixed", + left:0, top:0, width: "100%", height: "100%", + "background-color": "rgba(0, 0, 0, 0.8)", 'z-index':2000, 'padding':'3px'} ); + $('#_code').hide(); + $('#_code').click(closePopup); +} +// --- end of code popup section --- diff --git a/Scratch/en/blog/2010-06-19-jQuery-popup-the-easy-way/index.html b/Scratch/en/blog/2010-06-19-jQuery-popup-the-easy-way/index.html new file mode 100644 index 0000000..93cffe5 --- /dev/null +++ b/Scratch/en/blog/2010-06-19-jQuery-popup-the-easy-way/index.html @@ -0,0 +1,149 @@ + + + + + + YBlog - jQuery popup the easy way + + + + + + + + + + + + + +
    + + +
    +

    jQuery popup the easy way

    +
    +
    +
    +
    +

    Here is a fast and easy way to create jQuery popup.

    +
    // --- code popup ---
    +function openPopup() {
    +    $(this).clone(false).appendTo($("#_code"));
    +    $("#_code").show();
    +}
    +
    +function closePopup() {
    +    $("#_code").html("");
    +    $("#_code").hide();
    +}
    +
    +function initCode() {
    +    $(".code").click(openPopup);
    +    $(".code").css({cursor: "pointer"});
    +    $('body').append('<div id="_code"></div>');
    +    $('#_code').css( { 'text-align': "justify", position: "fixed", 
    +                        left:0, top:0, width: "100%", height: "100%", 
    +                        "background-color": "rgba(0, 0, 0, 0.8)", 'z-index':2000, 'padding':'3px'} );
    +    $('#_code').hide();
    +    $('#_code').click(closePopup);
    +}
    +// --- end of code popup section ---
    +

    What does this code do?

    +

    At the loading of the page, I create a div as wide as the window. This div is a bit transparent. Then I hide it. I also take care to its z-index value to be sure it is behind all elements.

    +

    Then when we click on a div of class code, I copy the content into this new wide div, and I show it. Really simple but really efficient. No need to use a jQuery plugin.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-06-19 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-07-05-Cappuccino-and-Web-applications/index.html b/Scratch/en/blog/2010-07-05-Cappuccino-and-Web-applications/index.html new file mode 100644 index 0000000..131536a --- /dev/null +++ b/Scratch/en/blog/2010-07-05-Cappuccino-and-Web-applications/index.html @@ -0,0 +1,214 @@ + + + + + + YBlog - Cappuccino vs jQuery + + + + + + + + + + + + + +
    + + +
    +

    Cappuccino vs jQuery

    +
    +
    +
    +
    +
    + +

    tl;dr:

    +
      +
    • Tried to make YPassword in jQuery and with Cappuccino.
    • +
    • Cappuccino nice in desktop browser but 1.4MB, not compatible with iPhone.
    • +
    • jQuery not as nice as the Cappuccino version but 106KB. iPhone compatible.
    • +
    • I’ll give a try to Dashcode 3.
    • +
    +
    + +
    +
    + +

    Before start, I must say I know Cappuccino and jQuery are no more comparable than Cocoa and the C++ standard library. One is oriented for user interface while the other is and helper for low level programming. Nonetheless I used these two to make the same web application. This is why I compare the experience I had with each of them for this specific task.

    +
    + +

    I made a web version of my dashboard widget YPassword. It is a simple widget to manage your online password with a strong security and with a totally portable way. It is not intended to replace a keychain. It is more a password generator.

    +

    The first was made from the code of my dashboard widget and with some jQuery. You can try it here. I then made a second version with the Cappuccino. You can try it here.

    +

    What this widget do?

    +
    + +

    If you don’t mind about what does my widget and just want to know how the two frameworkcompare, you should go directly to the next part.

    +
    + +

    I manage my password on many site with a simple method. I remember a strong master password. And my password is mainly hash(masterPassword+domainName)

    +

    In reality I need a bit more informations to create a password:

    +
      +
    • A master password,
    • +
    • an URL,
    • +
    • a maximal password length,
    • +
    • the kind of output base64 or hexadecimal,
    • +
    • how many times my password could have leaked.
    • +
    +

    The result password is this:

    +
    domainName=domaine_Name_Of_URL(url)
    +hash=sha1( masterPassword + leakedTimes + domainName )
    +if ( kind == 'base64' )
    +    hash=base64(hash)
    +end
    +return hash[0..maxlength]
    +

    In fact depending of websites, some give some strange constraint to your password:

    +
      +
    • minimal length,
    • +
    • maximal length,
    • +
    • must not contain a special character,
    • +
    • must contain a special character,
    • +
    • etc…
    • +
    +

    And if you want to change your password the leak number is here for that. All informations such as user name, maximal length can be stored in a public file. The only real secret is the master password.

    +

    If you want to know even more details you can always look at some of my old blog entries:

    + +

    Cappuccino

    +

    First, I’d like to say Cappuccino applications look simply awesome. It is like having a Cocoa application in your web browser. And this is great.

    +

    I also must admit I enjoyed making my application with Cappuccino. It is like programming for an iPhone application. If you are a bit familiar with Cocoa, you feel at home. If you don’t know anything about Cocoa, I suggest you to look at it. This is a really great framework to make User Interface. I am not a specialist, but I have done some MFC, java Swing1 and WXWindows User Interfaces (some years ago). And I must say, Cocoa is far better than those.

    +

    Cappuccino is a great web application oriented development. But there was also some drawbacks

    +

    Things I liked:

    +
      +
    • It looks great
    • +
    • It was fun to program
    • +
    • It was like programming a Mac application
    • +
    • I could have done the User Interface using Interface Builder.
    • +
    +

    Some things I didn’t like:

    +
      +
    • I made some time to understand how to handle the onChange on the text fields.
    • +
    • Documentation lacked a bit of organisation.
    • +
    • It doesn’t work on iPhone.
    • +
    • It weighted 11MB to deploy.
    • +
    • It weight 1.3MB to load.
    • +
    +

    I didn’t use bindings because I believe they are not ready by now.

    +

    jQuery

    +

    The jQuery version of YPassword is not as finished as the Cappuccino one. Because, there is no slider directly with jQuery. I’d have to use jQueryUI. And I believe, using it will make the application weight far more than the today 106KB.

    +

    To make this version I simply copied my widget source code and adapted it. It was straightforward. But jQuery is not an application oriented framework. It is more a “dark side javascript animation framework”2.

    +

    I don’t have too much to say about the jQuery version. But this was way more low level programming than Cappuccino.

    +

    My conclusion

    +

    If you want to make an iPhone compatible web application just don’t use Cappuccino yet. If you want to make simple application like mine, I also believe, Cappuccino is a bit too much.

    +

    If you want to make a complex web oriented application, Cappuccino is a great choice. But you may have some difficulties to begin programming with it.

    +

    Finally, to terminate my web version of my widget, I’ll give a try to Dashcode 3. It seems to be a good alternative to create web widgets. I don’t know if Dashcode 3 is portable on non webkit browser. But if it is, it could be the end of projects like Cappuccino and Sproutcore.

    +
    +
    +
      +
    1. If you are interested you can take a look at SEDiL. I am proud of the tree drawing view made from scratch.

    2. +
    3. I don’t want to feel like a troll I use jQuery to make some dark side animation on this blog. But the javascript on my blog is not needed except for commenting.

    4. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-07-05 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-07-07-CSS-rendering-problems-by-navigator/index.html b/Scratch/en/blog/2010-07-07-CSS-rendering-problems-by-navigator/index.html new file mode 100644 index 0000000..156547f --- /dev/null +++ b/Scratch/en/blog/2010-07-07-CSS-rendering-problems-by-navigator/index.html @@ -0,0 +1,138 @@ + + + + + + YBlog - Do not use CSS gradient with Chrome + + + + + + + + + + + + + +
    + + +
    +

    Do not use CSS gradient with Chrome

    +
    +
    +
    +
    +

    Some Reddit users reported my website was really long to load and to scroll. They thinks it was because of the ‘1px shadow’ I apply on all the text. I was a bit surprised, because I make some test into a really slow virtual machine. And all have always worked fine. In fact, what slow down so much are by order of importance:

    +
      +
    1. Radial gradient on Chrome (not in Safari on Mac)
    2. +
    3. Box shadows on Firefox and Chrome
    4. +
    +

    Gradient

    +

    On Safari on Mac there is absolutely no rendering time problem. But when I use Chrome under Linux it is almost unusable.

    +

    Safari and Chrome use webkit, when you access my website with javascript enabled, an additionnal browser specific CSS is loaded. Until now I switched only between: IE, Mozilla and Webkit. Now I added one more special case for Chrome. Now I continue to use gradient for Safari but no more on Chrome.

    +

    I didn’t tried to verify the efficiency of all new CSS 3 features. But I advise you not to use -webkit-gradient on Chrome. At least when the host is a Linux.

    +

    Box Shadows

    +

    I also detected that -moz-box-shadow elements slow down the rendering on Firefox under Linux. But there was very few time rendering issue with Safari on Mac.

    +

    Text Shadows

    +

    Many tell me to use text-shadows sparingly. But I believe it was not the real reason of the slow down. This is why I’ll get them back.

    +

    Conclusion

    +

    Do not use -webkit-gradient on Chrome browser yet. Try to use -moz-box-shadow sparingly.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-07-07 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-07-09-Indecidabilities/index.html b/Scratch/en/blog/2010-07-09-Indecidabilities/index.html new file mode 100644 index 0000000..0560608 --- /dev/null +++ b/Scratch/en/blog/2010-07-09-Indecidabilities/index.html @@ -0,0 +1,213 @@ + + + + + + YBlog - Undecidabilities (part 1) + + + + + + + + + + + + + +
    + + +
    +

    Undecidabilities (part 1)

    +
    +
    +
    +
    +

    <% # toremove_ %>

    +
    + +

    tl;dr: I pretend to create a world to give examples of different meanings behind the word undecidability:

    +
      +
    • Undecidability due to measure errors,
    • +
    • Big errors resulting from small initial measure error,
    • +
    • Fractal undecidability ;
    • +
    • Logic Undecidability.
    • +
    +
    + +
    +

    The Undecidabilities

    +
    + +

    If a demiurge made our world, he certainly had a great sense of humor. After this read, you should be convinced. I’ll pretend to be him. I’ll create a simplified world. A world that obey to simple mathematical rules. And I’ll tell you about one of the curse on this world: the undecidability. The inability to know if we had find the truth. The inability to predict many things that should be natural. Here begin the story.

    +
    + +

    +

    In the beginning there was only void. Then a blog post beginning to be written. I breath profoundly to feel the weight of the act I will accomplish. A last tense moment and… I create the Universe. An incredible Universe which will exists only the time of this read. I’m the demiurge of this universe and you are its observer.

    +

    I construct this world using only simples rules. I decide that real rules of this world will be the one we believe are true for our world. Note the difference. For their world, everything we believe today is true for them. Their world is then probably simpler than our. Particularly, we can describe this world with axioms and mathematic rules. It is not so sure for our Universe. But we’ll talk about that later.

    +

    Lets the work begin. I create an Earth. I populate it with intelligent people, the Ys. Of course they are curious. In particular they try to understand their world. They believe that if they know the rules of their world they will be able to predict the consequences of most of their acts. They are so naive. If only they knew. But I’m here to help them.

    +

    I am a God who likes jokes. The first joke I make to Ys is to make their sense imperfect. Furthermore it is not possible to make perfect precise measure in my world. I let Ys ameliorate their technology but there is a theoretical limit to the best precision they can reach.

    +

    I’d like to precise that these people believe their world is flat. Some believe it is possible to find the rules of their Universe. Now, let the game begins.

    +

    Lets start easily, errors can cause undecidability.

    +

    Undecidability due to measure errors

    +

    Here is what one of them think:

    +
    +

    All triangle I observe seems to share the same property. Each time I sum up their angles I obtain π radiants (180°). It is certainly a rule of my Universe. But how to be certain all triangle in my Universe share this property?

    +
    +

    three triangles

    +

    Some began to formalize the problem. They end by writing a mathematical proof. Marvelous! The proof seems correct, but, a problem remains. The proof is based on rules and axioms. How to be certain these rules and axioms are right in their world? They will try to measure again and again the sum of the angles of triangles. The measure will never fail. But they’ll never be certain the rules and axioms are right. Because then only way to verify all axioms depends of observation. And as a facetious god, I forbid perfect measure in observation.

    +

    Of course, they prey, they call me to help. And as any respectful god, I don’t answer. Ah ah ah! I’ve always loved to make these kind of thing. Let’s act as if I don’t exists. What a good joke!

    +

    They feel sad. But they have some hope:

    +

    Hope

    +
    +

    If we make small measure error, we will make small predictive error.

    +
    +

    Growing errors Undecidability

    +

    Three bodies

    +

    Unfortunately, the three bodies problem will crush this hope. Using Newton’s Universal Law of gravitation with two bodies, we can predict with precision what will be their position and speed in the future. Until there all seems OK. But now, add another body. All errors will grow. Errors will grow at a point that any prediction will be unusable.

    +

    Even with this bad news there is the hope to control the error.

    +
    +

    May we should know the maximal measure error we can handle to predict something. And we should at least determine what we can predict and what we cannot.

    +
    +

    Once again, this should not terminate has they hope.

    +

    Fractal Undecidability

    +

    Consider the following question:

    +

    Mandelbrot set

    +

    Consider some GPS coordinates on a point around the cost of the “Bretagne” in France. The coordinates are 3 feet precise. Is the point in the water or on Earth?

    +

    For some coordinates it is not possible to know. Even if we are authorize to move a bit to dodge the borders. Because there are some zone in which all point could be a “border” for any size of the zone.

    +

    We can even imagine some mathematical structure where all points are at the border1.

    +

    Logical Undecidability

    +

    recursive stack overflow

    +

    Until there all problem were undecidable because of measure errors. May be in a controlled world without any error we should be able to predict anything.
    I’m sorry to say no. Even in a self-contained mathematical world it can be possible to create object with an unpredictable behaviour.

    +

    It is the halting problem.

    +

    Theorem: It is undecidable given a description of a program, whether the program finishes running or will run forever. The idea of the proof is simple enough to be part of this article. And this is with pleasure I give you one here.

    +
    +

    Suppose a program able to decide if any program halt exists. More precisely:

    +

    Hypothesis: there exists a program P such that:

    +
      +
    • P(x,y) return “stop” in a finite amount of time if x(y)2 will stop running.
    • +
    • P(x,y) return “loop” in a finite amount of time if x(y) will never stop running.
    • +
    +

    Remark: Any program can be represented as a string. Therefore, a program can be used as the input of another program. It is authorized to write P(x,x).

    +

    Let Q be the following program using the return value of P.

    +
    +Q(x) :
    +    if P(x,x)="stop" then I enter in an infinite loop
    +    if P(x,x)="loop" then I stop
    +
    + +

    Now, what is the value of P(Q,Q)?

    +
      +
    • if P(Q,Q) returns “stop” that imply by construction of Q that P(Q,Q) returns “loop”.
    • +
    • if P(Q,Q) returns “loop” that means by construction of Q that P(Q,Q) return “stop”.
    • +
    +

    Therefore there is a contradiction the only way to handle is by the non existence of the program P.

    +
    +

    I am the demiurge of this imaginary world. And I cannot know the future of this world. Therefore, creative power isn’t equivalent to omnipotence.

    +
    +

    After all this, it becomes difficult to know what we can believe. But it would be another error to throw away all our knowledge. In a future next part, I’ll explain what we can hope and what attitude we should have once we’ve realized most of truth are unaccessible.

    +
    +
    +
      +
    1. The set Rhas this property.

    2. +
    3. Meaning x taking y as input.

    4. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-08-11 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-07-31-New-style-after-holidays/index.html b/Scratch/en/blog/2010-07-31-New-style-after-holidays/index.html new file mode 100644 index 0000000..c8886ae --- /dev/null +++ b/Scratch/en/blog/2010-07-31-New-style-after-holidays/index.html @@ -0,0 +1,125 @@ + + + + + + YBlog - New style after holidays + + + + + + + + + + + + + +
    + + +
    +

    New style after holidays

    +
    +
    +
    +
    +

    Before my holidays many visitors tell me my website was too long to scroll. This is why I completely changed my website design. Now all should scroll smoothly on all platforms. I was inspired by Readability and iBooks(c) (the iPhone(c) application).

    +

    Tell me what you think of this new design.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-07-31 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-08-23-Now-heberged-on-heroku/code/config.ru b/Scratch/en/blog/2010-08-23-Now-heberged-on-heroku/code/config.ru new file mode 100644 index 0000000..48ae352 --- /dev/null +++ b/Scratch/en/blog/2010-08-23-Now-heberged-on-heroku/code/config.ru @@ -0,0 +1,42 @@ +require 'rubygems' +require 'rack' +require 'rack/contrib' +require 'rack-rewrite' +require 'mime/types' + +use Rack::ETag +module ::Rack + class TryStatic < Static + + def initialize(app, options) + super + @try = ([''] + Array(options.delete(:try)) + ['']) + end + + def call(env) + @next = 0 + while @next < @try.size && 404 == (resp = super(try_next(env)))[0] + @next += 1 + end + 404 == resp[0] ? @app.call : resp + end + + private + def try_next(env) + env.merge('PATH_INFO' => env['PATH_INFO'] + @try[@next]) + end + + end +end + +use Rack::TryStatic, + :root => "output", # static files root dir + :urls => %w[/], # match all requests + :try => ['.html', 'index.html', '/index.html'] # try these postfixes sequentially + +errorFile='output/Scratch/en/error/404-not_found/index.html' +run lambda { [404, { + "Last-Modified" => File.mtime(errorFile).httpdate, + "Content-Type" => "text/html", + "Content-Length" => File.size(errorFile).to_s + }, File.read(errorFile)] } diff --git a/Scratch/en/blog/2010-08-23-Now-heberged-on-heroku/index.html b/Scratch/en/blog/2010-08-23-Now-heberged-on-heroku/index.html new file mode 100644 index 0000000..93d5670 --- /dev/null +++ b/Scratch/en/blog/2010-08-23-Now-heberged-on-heroku/index.html @@ -0,0 +1,185 @@ + + + + + + YBlog - Now hosted by heroku + + + + + + + + + + + + + +
    + + +
    +

    Now hosted by heroku

    +
    +
    +
    +
    +

    Now on Heroku

    +

    I now changed my hosting to Heroku. I believe it will be far more reliable.

    +

    But as you should know my website is completely static. I use nanoc to generate it. But here is the conf to make it work on heroku.

    +

    The root of my files is /output. You only need to create a config.ru1 file:

    +
    require 'rubygems'
    +require 'rack'
    +require 'rack/contrib'
    +require 'rack-rewrite'
    +require 'mime/types'
    +
    +use Rack::ETag
    +module ::Rack
    +    class TryStatic < Static
    +
    +        def initialize(app, options)
    +            super
    +            @try = ([''] + Array(options.delete(:try)) + [''])
    +        end
    +
    +        def call(env)
    +            @next = 0
    +            while @next < @try.size && 404 == (resp = super(try_next(env)))[0] 
    +                @next += 1
    +            end
    +            404 == resp[0] ? @app.call : resp
    +        end
    +
    +        private
    +        def try_next(env)
    +            env.merge('PATH_INFO' => env['PATH_INFO'] + @try[@next])
    +        end
    +
    +    end
    +end
    +
    +use Rack::TryStatic, 
    +    :root => "output",                              # static files root dir
    +    :urls => %w[/],                                 # match all requests 
    +    :try => ['.html', 'index.html', '/index.html']  # try these postfixes sequentially
    +
    +errorFile='output/Scratch/en/error/404-not_found/index.html'
    +run lambda { [404, {
    +                "Last-Modified"  => File.mtime(errorFile).httpdate,
    +                "Content-Type"   => "text/html",
    +                "Content-Length" => File.size(errorFile).to_s
    +            }, File.read(errorFile)] }
    +

    and the .gems file needed to install rack middlewares.

    +
    rack
    +rack-rewrite
    +rack-contrib
    +

    Now, just follow the heroku tutorial to create an application :

    +
    git init
    +git add .
    +heroku create
    +git push heroku master
    +

    Now I’ll should be able to redirect properly to my own 404 page for example. I hope it is helpful.

    +
    +
    +
      +
    1. I was inspired by this article.

    2. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-08-23 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-08-31-send-mail-from-command-line-with-attached-file/index.html b/Scratch/en/blog/2010-08-31-send-mail-from-command-line-with-attached-file/index.html new file mode 100644 index 0000000..58d82d3 --- /dev/null +++ b/Scratch/en/blog/2010-08-31-send-mail-from-command-line-with-attached-file/index.html @@ -0,0 +1,165 @@ + + + + + + YBlog - send mail from command line with attached file + + + + + + + + + + + + + +
    + + +
    +

    send mail from command line with attached file

    +
    +
    +
    +
    +

    I had to send a mail using only command line. I was surprised it isn’t straightforward at all. I didn’t had pine nor mutt or anything like that. Just mail and mailx.

    +

    What Internet say (via google) is

    +
    uuencode fic.jpg fic.jpg | mail -s 'Subject'
    +

    I tried it. And it works almost each times. But for my file, it didn’t worked. I compressed it to .gz, .bz2 and .zip. Using .bz2 format it worked nicely, but not with other formats. Instead of having an attached file I saw this in my email.

    +
    +begin 664 fic.jpg
    +M(R$O=7-R+V)I;B]E;G8@>G-H"GAL
    +                   
    +                   
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-08-31 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/code/gitmtime.rb b/Scratch/en/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/code/gitmtime.rb new file mode 100644 index 0000000..7aeec2b --- /dev/null +++ b/Scratch/en/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/code/gitmtime.rb @@ -0,0 +1,14 @@ +def gitmtime + filepath=@item.path.sub('/Scratch/','content/html/').sub(/\/$/,'') + ext=%{.#{@item[:extension]}} + filepath<<=ext + if not FileTest.exists?(filepath) + filepath.sub!(ext,%{#{@item.raw_filename}#{ext}}) + end + str=`git log -1 --format='%ci' -- #{filepath}` + if str.nil? or str.empty? + return Time.now + else + return DateTime.parse( str ) + end +end diff --git a/Scratch/en/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/index.html b/Scratch/en/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/index.html new file mode 100644 index 0000000..0535fd3 --- /dev/null +++ b/Scratch/en/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/index.html @@ -0,0 +1,141 @@ + + + + + + YBlog - Use git to calculate trusted mtimes + + + + + + + + + + + + + +
    + + +
    +

    Use git to calculate trusted mtimes

    +
    +
    +
    +
    +

    You can remark at the bottom of each page I provide a last modification date. This label was first calculated using the mtime of the file on the file system. But many times I modify this date just to force some recompilation. Therefore the date wasn’t a date of real modification.

    +

    I use git to version my website. And fortunately I can know the last date of real change of a file. This is how I do this with nanoc:

    +
    def gitmtime
    +    filepath=@item.path.sub('/Scratch/','content/html/').sub(/\/$/,'')
    +    ext=%{.#{@item[:extension]}}
    +    filepath<<=ext
    +    if not FileTest.exists?(filepath)
    +        filepath.sub!(ext,%{#{@item.raw_filename}#{ext}})
    +    end
    +    str=`git log -1 --format='%ci' -- #{filepath}`
    +    if str.nil? or str.empty?
    +        return Time.now
    +    else
    +        return DateTime.parse( str )
    +    end
    +end
    +

    Of course I know it is really slow and absolutely not optimized. But it works as expected. Now the date you see at the bottom is exactly the date I modified the content of the page.

    +

    Edit: Thanks to Eric Sunshine and Kris to provide me some hints at cleaning my code.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-09-02 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-09-02-base64-and-sha1-on-iPhone/code/iphone_base64_sha1.c b/Scratch/en/blog/2010-09-02-base64-and-sha1-on-iPhone/code/iphone_base64_sha1.c new file mode 100644 index 0000000..cd98792 --- /dev/null +++ b/Scratch/en/blog/2010-09-02-base64-and-sha1-on-iPhone/code/iphone_base64_sha1.c @@ -0,0 +1,42 @@ + +- (unsigned char *)sha1:(NSString *)baseString result:(unsigned char *)result { + char *c_baseString=(char *)[baseString UTF8String]; + CC_SHA1(c_baseString, strlen(c_baseString), result); + return result; +} + +- (NSString *)base64:(unsigned char *)result { + NSString *password=[[NSString alloc] init]; + static const unsigned char cb64[65]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + for (int i=0; i>2], + cb64[((result[i] & 0x03) << 4) + | ((result[i + 1] & 0xF0) >> 4)], + cb64[((result[i + 1] & 0x0F) << 2) + | ((result[i + 2] & 0xC0) >> 6)], + cb64[result[i+2]&0x3F] + ]; + } + return password; +} + +- (NSString *)hexadecimalRepresentation:(unsigned char *)result { + NSString *password=[[NSString alloc] init]; + for (int i=0; i + + + + + YBlog - base64 and sha1 on iPhone + + + + + + + + + + + + + +
    + + +
    +

    base64 and sha1 on iPhone

    +
    +
    +
    +
    +

    Lets be straight: here are two functions to add to your code to have base64 and hexadecimal version of the sha1 hash of an NSString.

    +

    To use it, simply copy the code in your class and use as this:

    +
    #import <CommonCrypto/CommonDigest.h>
    +...
    +NSString *b64_hash = [self b64_sha1:@"some NSString to be sha1'ed"];
    +...
    +NSString *hex_hash = [self hex_sha1:@"some NSString to be sha1'ed"];
    +

    The base64 algorithm must be programmed by hand on iPhone!

    +
    
    +- (unsigned char *)sha1:(NSString *)baseString result:(unsigned char *)result {
    +    char *c_baseString=(char *)[baseString UTF8String];
    +    CC_SHA1(c_baseString, strlen(c_baseString), result);
    +    return result;
    +}
    +
    +- (NSString *)base64:(unsigned char *)result {
    +    NSString *password=[[NSString alloc] init];
    +    static const unsigned char cb64[65]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    +    for (int i=0; i<CC_SHA1_DIGEST_LENGTH; i+=3) {
    +        password=[password stringByAppendingFormat:@"%c%c%c%c",
    +            cb64[(result[i] &0xFC)>>2],
    +            cb64[((result[i] & 0x03) << 4)
    +                | ((result[i + 1] & 0xF0) >> 4)],
    +            cb64[((result[i + 1] & 0x0F) << 2)
    +                | ((result[i + 2] & 0xC0) >> 6)],
    +            cb64[result[i+2]&0x3F]
    +                ];            
    +    }
    +    return password;
    +}
    +
    +- (NSString *)hexadecimalRepresentation:(unsigned char *)result {
    +    NSString *password=[[NSString alloc] init];
    +    for (int i=0; i<CC_SHA1_DIGEST_LENGTH; i++) {
    +        password=[password stringByAppendingFormat:@"%02x", result[i]];
    +    }
    +    return password;
    +}
    +
    +- (NSString *)b64_sha1:(NSString *)inputString {
    +    unsigned char result[CC_SHA1_DIGEST_LENGTH+1];
    +    [self sha1:inputString result:result];
    +    return [self base64:result];
    +}
    +
    +- (NSString *)hex_sha1:(NSString *)inputString {
    +    unsigned char result[CC_SHA1_DIGEST_LENGTH+1];
    +    [self sha1:inputString result:result];
    +    return [self hexadecimalRepresentation:result];
    +}
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-09-02 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-10-06-New-Blog-Design-Constraints/index.html b/Scratch/en/blog/2010-10-06-New-Blog-Design-Constraints/index.html new file mode 100644 index 0000000..dff60a2 --- /dev/null +++ b/Scratch/en/blog/2010-10-06-New-Blog-Design-Constraints/index.html @@ -0,0 +1,130 @@ + + + + + + YBlog - New Blog Design Constraints + + + + + + + + + + + + + +
    + + +
    +

    New Blog Design Constraints

    +
    +
    +
    +
    +

    I changed the design of my blog. Now it should be far cleaner. I believe I use no CSS3 feature and far less javascript. Of course before my website was perfectly browsable without javascript. Unfortunately some CSS3 feature are not mature enough on some browser. For more details you can read my older blog entry. But the major problem came from, font-shadow and gradients. Then my new design obey to the following rules:

    +
      +
    • no CSS element begining by ‘-moz’ or ‘-webkit’, etc…,
    • +
    • no text shadow,
    • +
    • clean (I mean delete) most javascript.
    • +
    +

    I hope the new design please you.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-10-06 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/index.html b/Scratch/en/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/index.html new file mode 100644 index 0000000..1c431ba --- /dev/null +++ b/Scratch/en/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/index.html @@ -0,0 +1,149 @@ + + + + + + YBlog - Secure eMail on Mac in few steps + + + + + + + + + + + + + +
    + + +
    +

    Secure eMail on Mac in few steps

    +
    +
    +
    +
    +

    Title image

    +
    + +

    tl;dr: on Mac

    +
      +
    • Get a certificate signed by a CA: click here for a free one,
    • +
    • open the file,
    • +
    • delete securely the file,
    • +
    • use Mail instead of online gmail.
    • +
    • ???
    • +
    • Profit
    • +
    +
    + +

    I’ve (re)discovered how to become S/MIME compliant. I am now suprised how easy it was. Some years ago it was far more difficult. Now I’m able to sign and encrypt my emails.

    +

    Why is it important?

    +

    Signing: it tell the other with an aboslute certitude the writer of the mail is you or at least used your computer.

    +

    Encrypt: because sometimes you need to be 100% sure a conversation remains private.

    +

    How to proceed?

    +
      +
    • Get a certificate signed by a CA: click here to get a free one,
    • +
    • open the file,
    • +
    • empty your trash, put the file in the trash, secure empty trash,
    • +
    • use Mail instead of online gmail. Now you should see these icons: Sign icon
    • +
    +

    n.b.: if you use gmail, you and work not alway with a Mac, you should consider to try the gmail S/MIME firefox addon.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-10-10 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.c b/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.c new file mode 100644 index 0000000..aa39828 --- /dev/null +++ b/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.c @@ -0,0 +1,58 @@ +#include +#include +#include + +struct wavfile +{ + char id[4]; // should always contain "RIFF" + int totallength; // total file length minus 8 + char wavefmt[8]; // should be "WAVEfmt " + int format; // 16 for PCM format + short pcm; // 1 for PCM format + short channels; // channels + int frequency; // sampling frequency + int bytes_per_second; + short bytes_by_capture; + short bits_per_sample; + char data[4]; // should always contain "data" + int bytes_in_data; +}; + +int main(int argc, char *argv[]) { + char *filename=argv[1]; + FILE *wav = fopen(filename,"rb"); + struct wavfile header; + + if ( wav == NULL ) { + fprintf(stderr,"Can't open input file %s", filename); + exit(1); + } + + + // read header + if ( fread(&header,sizeof(header),1,wav) < 1 ) + { + fprintf(stderr,"Can't read file header\n"); + exit(1); + } + if ( header.id[0] != 'R' + || header.id[1] != 'I' + || header.id[2] != 'F' + || header.id[3] != 'F' ) { + fprintf(stderr,"ERROR: Not wav format\n"); + exit(1); + } + + fprintf(stderr,"wav format\n"); + + // read data + long sum=0; + short value=0; + while( fread(&value,sizeof(value),1,wav) ) { + // fprintf(stderr,"%d\n", value); + if (value<0) { value=-value; } + sum += value; + } + printf("%ld\n",sum); + exit(0); +} diff --git a/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.py b/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.py new file mode 100644 index 0000000..c0d4001 --- /dev/null +++ b/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +from struct import calcsize, unpack +from sys import argv, exit + +def word_iter(f): + while True: + _bytes = f.read(2) + + if len(_bytes) != 2: + raise StopIteration + + yield unpack("=h", _bytes)[0] + +try: + with open(argv[1], "rb") as f: + wav = "=4ci8cihhiihh4ci" + wav_size = calcsize(wav) + metadata = unpack(wav, f.read(wav_size)) + + if "".join(metadata[:4]) != "RIFF": + print "error: not wav file." + exit(1) + + print sum(abs(word) for word in word_iter(f)) +except IOError: + print "error: can't open input file '%s'." % argv[1] + exit(1) diff --git a/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.rb b/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.rb new file mode 100644 index 0000000..aa3c906 --- /dev/null +++ b/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.rb @@ -0,0 +1,11 @@ +data = ARGF.read + keys = %w[id totallength wavefmt format + pcm channels frequency bytes_per_second + bytes_by_capture bits_per_sample + data bytes_in_data sum + ] + values = data.unpack 'Z4 i Z8 i s s i i s s Z4 i s*' + sum = values.drop(12).map(&:abs).inject(:+) + keys.zip(values.take(12) << sum) {|k, v| + puts "#{k.ljust 17}: #{v}" + } diff --git a/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum2.c b/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum2.c new file mode 100644 index 0000000..48b7138 --- /dev/null +++ b/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum2.c @@ -0,0 +1,106 @@ +#include +#include +#include // for memcmp +#include // for int16_t and int32_t + +struct wavfile +{ + char id[4]; // should always contain "RIFF" + int32_t totallength; // total file length minus 8 + char wavefmt[8]; // should be "WAVEfmt " + int32_t format; // 16 for PCM format + int16_t pcm; // 1 for PCM format + int16_t channels; // channels + int32_t frequency; // sampling frequency + int32_t bytes_per_second; + int16_t bytes_by_capture; + int16_t bits_per_sample; + char data[4]; // should always contain "data" + int32_t bytes_in_data; +} __attribute__((__packed__)); + +int is_big_endian(void) { + union { + uint32_t i; + char c[4]; + } bint = {0x01000000}; + return bint.c[0]==1; +} + +int main(int argc, char *argv[]) { + char *filename=argv[1]; + FILE *wav = fopen(filename,"rb"); + struct wavfile header; + + if ( wav == NULL ) { + fprintf(stderr,"Can't open input file %s\n", filename); + exit(1); + } + + + // read header + if ( fread(&header,sizeof(header),1,wav) < 1 ) { + fprintf(stderr,"Can't read input file header %s\n", filename); + exit(1); + } + + // if wav file isn't the same endianness than the current environment + // we quit + if ( is_big_endian() ) { + if ( memcmp( header.id,"RIFX", 4) != 0 ) { + fprintf(stderr,"ERROR: %s is not a big endian wav file\n", filename); + exit(1); + } + } else { + if ( memcmp( header.id,"RIFF", 4) != 0 ) { + fprintf(stderr,"ERROR: %s is not a little endian wav file\n", filename); + exit(1); + } + } + + if ( memcmp( header.wavefmt, "WAVEfmt ", 8) != 0 + || memcmp( header.data, "data", 4) != 0 + ) { + fprintf(stderr,"ERROR: Not wav format\n"); + exit(1); + } + if (header.format != 16) { + fprintf(stderr,"\nERROR: not 16 bit wav format."); + exit(1); + } + fprintf(stderr,"format: %d bits", header.format); + if (header.format == 16) { + fprintf(stderr,", PCM"); + } else { + fprintf(stderr,", not PCM (%d)", header.format); + } + if (header.pcm == 1) { + fprintf(stderr, " uncompressed" ); + } else { + fprintf(stderr, " compressed" ); + } + fprintf(stderr,", channel %d", header.pcm); + fprintf(stderr,", freq %d", header.frequency ); + fprintf(stderr,", %d bytes per sec", header.bytes_per_second ); + fprintf(stderr,", %d bytes by capture", header.bytes_by_capture ); + fprintf(stderr,", %d bits per sample", header.bytes_by_capture ); + fprintf(stderr,"\n" ); + + if ( memcmp( header.data, "data", 4) != 0 ) { + fprintf(stderr,"ERROR: Prrroblem?\n"); + exit(1); + } + fprintf(stderr,"wav format\n"); + + // read data + long long sum=0; + int16_t value; + int i=0; + fprintf(stderr,"---\n", value); + while( fread(&value,sizeof(value),1,wav) ) { + if (value<0) { value=-value; } + sum += value; + } + printf("%lld\n",sum); + exit(0); +} diff --git a/Scratch/en/blog/2010-10-14-Fun-with-wav/index.html b/Scratch/en/blog/2010-10-14-Fun-with-wav/index.html new file mode 100644 index 0000000..195f98f --- /dev/null +++ b/Scratch/en/blog/2010-10-14-Fun-with-wav/index.html @@ -0,0 +1,373 @@ + + + + + + YBlog - Fun with wav + + + + + + + + + + + + + +
    + + +
    +

    Fun with wav

    +
    +
    +
    +
    +
    + +

    tl;dr: Played to process a wav file. C was easier and cleaner than Ruby.

    +

    edit: I wanted this program to work only on one specific machine (a x86 on a 32 bit Ubuntu). Therefore I didn’t had any portability consideration. This is only a hack.

    +
    + +

    I had to compute the sum of the absolute values of data of a .wav file. For efficiency (and fun) reasons, I had chosen C language.

    +

    I didn’t programmed in C for a long time. From my memory it was a pain to read and write to files. But in the end I was really impressed by the code I get. It was really clean. This is even more impressive knowing I used mostly low level functions.

    +

    A wav file has an header containing many metadata. This header was optimized to take as few space as possible. The header is then a block of packed bytes.

    +
      +
    • The 4th first bytes must contains RIFF in ASCII,
    • +
    • the following 4th Bytes is an 32 bits integer giving the size of the file minus 8, etc…
    • +
    +

    Surprisingly, I believe that reading this kind of file is easier in C than in most higher level language. Proof: I only have to search on the web the complete header format and write it in a struct.

    +
    struct wavfile
    +{
    +    char        id[4];          // should always contain "RIFF"
    +    int     totallength;    // total file length minus 8
    +    char        wavefmt[8];     // should be "WAVEfmt "
    +    int     format;         // 16 for PCM format
    +    short     pcm;            // 1 for PCM format
    +    short     channels;       // channels
    +    int     frequency;      // sampling frequency
    +    int     bytes_per_second;
    +    short     bytes_by_capture;
    +    short     bits_per_sample;
    +    char        data[4];        // should always contain "data"
    +    int     bytes_in_data;
    +};
    +

    To read this kind of data in Ruby, I certainly had to write a block of code for each element in the struct. But in C I simply written:

    +
    fread(&header,sizeof(header),1,wav)
    +

    Only one step to fill my data structure. Magic!

    +

    Then, get an int value coded on two Bytes is also not a natural operation for high level language. In C, to read a sequence of 2 Bytes numbers I only had to write:

    +
    short value=0;
    +while( fread(&value,sizeof(value),1,wav) ) {
    +    // do something with value
    +}
    +

    Finally I ended with the following code. Remark I know the wav format (16 bit / 48000Hz):

    +
    #include <stdio.h>
    +#include <stdlib.h>
    +#include <stdint.h>
    +
    +struct wavfile
    +{
    +    char        id[4];          // should always contain "RIFF"
    +    int     totallength;    // total file length minus 8
    +    char        wavefmt[8];     // should be "WAVEfmt "
    +    int     format;         // 16 for PCM format
    +    short     pcm;            // 1 for PCM format
    +    short     channels;       // channels
    +    int     frequency;      // sampling frequency
    +    int     bytes_per_second;
    +    short     bytes_by_capture;
    +    short     bits_per_sample;
    +    char        data[4];        // should always contain "data"
    +    int     bytes_in_data;
    +};
    +
    +int main(int argc, char *argv[]) {
    +    char *filename=argv[1];
    +    FILE *wav = fopen(filename,"rb");
    +    struct wavfile header;
    +
    +    if ( wav == NULL ) {
    +        fprintf(stderr,"Can't open input file %s", filename);
    +        exit(1);
    +    }
    +
    +    // read header
    +    if ( fread(&header,sizeof(header),1,wav) < 1 )
    +    {
    +        fprintf(stderr,"Can't read file header\n");
    +        exit(1);
    +    }
    +    if (    header.id[0] != 'R'
    +         || header.id[1] != 'I' 
    +         || header.id[2] != 'F' 
    +         || header.id[3] != 'F' ) { 
    +        fprintf(stderr,"ERROR: Not wav format\n"); 
    +        exit(1); 
    +    }
    +
    +    fprintf(stderr,"wav format\n");
    +
    +    // read data
    +    long sum=0;
    +    short value=0;
    +    while( fread(&value,sizeof(value),1,wav) ) {
    +        // fprintf(stderr,"%d\n", value);
    +        if (value<0) { value=-value; }
    +        sum += value;
    +    }
    +    printf("%ld\n",sum);
    +    exit(0);
    +}
    +

    Of course it is only a hack. But we can see how easy and clean it should be to improve. As I say often: the right tool for your need instead of the same tool for all your needs. Because here C is clearly far superior than Ruby to handle this simple tasks.

    +

    I am curious to know if somebody know a nice way to do this with Ruby or Python.

    +

    edit: for compatibility reasons (64bit machines) used int16_t instead of short and int instead of int.

    +
    + +

    Edit (2): after most consideration about portability I made an hopefully more portable version. But I must confess this task was a bit tedious. The code remain as readable as before. But I had to use some compiler specific declaration to force the structure to be packed:

    +
    __attribute__((__packed__))
    +

    Therefore this implementation should for big and little endian architecture. However, it must be compiled with gcc. The new code make more tests but still don’t use mmap. Here it is:

    +
    + +
    #include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h> // for memcmp
    +#include <stdint.h> // for int16_t and int32_t
    +
    +struct wavfile
    +{
    +    char    id[4];          // should always contain "RIFF"
    +    int32_t totallength;    // total file length minus 8
    +    char    wavefmt[8];     // should be "WAVEfmt "
    +    int32_t format;         // 16 for PCM format
    +    int16_t pcm;            // 1 for PCM format
    +    int16_t channels;       // channels
    +    int32_t frequency;      // sampling frequency
    +    int32_t bytes_per_second;
    +    int16_t bytes_by_capture;
    +    int16_t bits_per_sample;
    +    char    data[4];        // should always contain "data"
    +    int32_t bytes_in_data;
    +} __attribute__((__packed__));
    +
    +int is_big_endian(void) {
    +    union {
    +        uint32_t i;
    +        char c[4];
    +    } bint = {0x01000000};
    +    return bint.c[0]==1;
    +}
    +
    +int main(int argc, char *argv[]) {
    +    char *filename=argv[1];
    +    FILE *wav = fopen(filename,"rb");
    +    struct wavfile header;
    +
    +    if ( wav == NULL ) {
    +        fprintf(stderr,"Can't open input file %s\n", filename);
    +        exit(1);
    +    }
    +
    +    // read header
    +    if ( fread(&header,sizeof(header),1,wav) < 1 ) {
    +        fprintf(stderr,"Can't read input file header %s\n", filename);
    +        exit(1);
    +    }
    +
    +    // if wav file isn't the same endianness than the current environment
    +    // we quit
    +    if ( is_big_endian() ) {
    +        if (   memcmp( header.id,"RIFX", 4) != 0 ) {
    +            fprintf(stderr,"ERROR: %s is not a big endian wav file\n", filename); 
    +            exit(1);
    +        }
    +    } else {
    +        if (   memcmp( header.id,"RIFF", 4) != 0 ) {
    +            fprintf(stderr,"ERROR: %s is not a little endian wav file\n", filename); 
    +            exit(1);
    +        }
    +    }
    +
    +    if (   memcmp( header.wavefmt, "WAVEfmt ", 8) != 0 
    +        || memcmp( header.data, "data", 4) != 0 
    +            ) {
    +        fprintf(stderr,"ERROR: Not wav format\n"); 
    +        exit(1); 
    +    }
    +    if (header.format != 16) {
    +        fprintf(stderr,"\nERROR: not 16 bit wav format.");
    +        exit(1);
    +    }
    +    fprintf(stderr,"format: %d bits", header.format);
    +    if (header.format == 16) {
    +        fprintf(stderr,", PCM");
    +    } else {
    +        fprintf(stderr,", not PCM (%d)", header.format);
    +    }
    +    if (header.pcm == 1) {
    +        fprintf(stderr, " uncompressed" );
    +    } else {
    +        fprintf(stderr, " compressed" );
    +    }
    +    fprintf(stderr,", channel %d", header.pcm);
    +    fprintf(stderr,", freq %d", header.frequency );
    +    fprintf(stderr,", %d bytes per sec", header.bytes_per_second );
    +    fprintf(stderr,", %d bytes by capture", header.bytes_by_capture );
    +    fprintf(stderr,", %d bits per sample", header.bytes_by_capture );
    +    fprintf(stderr,"\n" );
    +
    +    if ( memcmp( header.data, "data", 4) != 0 ) { 
    +        fprintf(stderr,"ERROR: Prrroblem?\n"); 
    +        exit(1); 
    +    }
    +    fprintf(stderr,"wav format\n");
    +
    +    // read data
    +    long long sum=0;
    +    int16_t value;
    +    int i=0;
    +    fprintf(stderr,"---\n", value);
    +    while( fread(&value,sizeof(value),1,wav) ) {
    +        if (value<0) { value=-value; }
    +        sum += value;
    +    }
    +    printf("%lld\n",sum);
    +    exit(0);
    +}
    +

    Edit(3): On reddit Bogdanp proposed a Python version:

    +
    #!/usr/bin/env python
    +from struct import calcsize, unpack
    +from sys import argv, exit
    +
    +def word_iter(f):
    +    while True:
    +        _bytes = f.read(2)
    +
    +    if len(_bytes) != 2:
    +        raise StopIteration
    +
    +    yield unpack("=h", _bytes)[0]
    +
    +try:
    +    with open(argv[1], "rb") as f:
    +        wav = "=4ci8cihhiihh4ci"
    +        wav_size = calcsize(wav)
    +        metadata = unpack(wav, f.read(wav_size))
    +
    +        if "".join(metadata[:4]) != "RIFF":
    +            print "error: not wav file."
    +            exit(1)
    +
    +        print sum(abs(word) for word in word_iter(f))
    +except IOError:
    +    print "error: can't open input file '%s'." % argv[1]
    +    exit(1)
    +

    and luikore proposed an impressive Ruby version:

    +
    data = ARGF.read
    + keys = %w[id totallength wavefmt format
    +       pcm channels frequency bytes_per_second
    +         bytes_by_capture bits_per_sample
    +           data bytes_in_data sum
    + ]
    + values = data.unpack 'Z4 i Z8 i s s i i s s Z4 i s*'
    + sum = values.drop(12).map(&:abs).inject(:+)
    + keys.zip(values.take(12) << sum) {|k, v|
    +       puts "#{k.ljust 17}: #{v}"
    + }
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-10-14 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2010-10-26-LaTeX-like-macro-and-markdown/code/macros.rb b/Scratch/en/blog/2010-10-26-LaTeX-like-macro-and-markdown/code/macros.rb new file mode 100644 index 0000000..17ebf55 --- /dev/null +++ b/Scratch/en/blog/2010-10-26-LaTeX-like-macro-and-markdown/code/macros.rb @@ -0,0 +1,43 @@ +# usage: +# --- +# ... +# macros: +# test: "passed test" +# --- +# ... +# Here is a %test. +# +class Macros < Nanoc3::Filter + identifier :falacy + attr_accessor :macro + def initialize(arg) + super + @macro={} + @macro[:tlal] = %{tlàl : } + @macro[:tldr] = %{tl;dr: } + if @item.nil? + if not arg.nil? + @macro.merge!( arg ) + end + else + if not @item[:macros].nil? + @macro.merge!( @item[:macros] ) + end + end + end + def macro_value_for(macro_name) + if macro_name.nil? or macro_name=="" or @macro[macro_name.intern].nil? + return %{%#{macro_name}} + end + return @macro[macro_name.intern] + end + def run(content, params={}) + content.gsub(/%(\w*)/) do |m| + if m != '%' + macro_value_for($1) + else + m + end + end + end +end diff --git a/Scratch/en/blog/2010-10-26-LaTeX-like-macro-and-markdown/index.html b/Scratch/en/blog/2010-10-26-LaTeX-like-macro-and-markdown/index.html new file mode 100644 index 0000000..3580596 --- /dev/null +++ b/Scratch/en/blog/2010-10-26-LaTeX-like-macro-and-markdown/index.html @@ -0,0 +1,180 @@ + + + + + + YBlog - LaTeX like macro for markdown + + + + + + + + + + + + + +
    + + +
    +

    LaTeX like macro for markdown

    +
    +
    +
    +
    +
    + +

    tl;dr: I made a simple macro system for my blog. Now I juste have to write %latex and it show as LaTeX.

    +
    + +

    I added a macro system for my blog system. When we are used to LaTeX this lack can be hard to handle. Particularly when using mathematical notations. In the header of my files I simply write:

    +
    +

    In the body it will replace every occurrence of:

    +
      +
    • %test by Just a test,
    • +
    • and %latex by LaTeX.
    • +
    +

    The source code is really simple. For nanoc user, simply put this file in your lib directory.

    +
    # usage:
    +# ---
    +# ...
    +# macros:
    +#   test: "passed test"
    +# ---
    +# ...
    +# Here is a Just a test.
    +#
    +class Macros < Nanoc3::Filter
    +    identifier :falacy
    +    attr_accessor :macro
    +    def initialize(arg)
    +        super
    +        @macro={}
    +        @macro[:tlal] = %{<span class="sc"><abbr title="Trop long à lire">tlàl</abbr> : </span>}
    +        @macro[:tldr] = %{<span class="sc"><abbr title="Too long; didn't read">tl;dr</abbr>: </span>}
    +        if @item.nil?
    +            if not arg.nil?
    +                @macro.merge!( arg )
    +            end
    +        else
    +            if not @item[:macros].nil?
    +                @macro.merge!( @item[:macros] )
    +            end
    +        end
    +    end
    +    def macro_value_for(macro_name)
    +        if macro_name.nil? or macro_name=="" or @macro[macro_name.intern].nil?
    +            return %{%#{macro_name}} 
    +        end
    +        return @macro[macro_name.intern]
    +    end
    +    def run(content, params={})
    +        content.gsub(/%(\w*)/) do |m| 
    +            if m != '%'
    +                macro_value_for($1)
    +            else
    +                m
    +            end
    +        end
    +    end
    +end
    +

    Macros could be very useful, read this article for example.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-10-26 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2011-01-03-Happy-New-Year/index.html b/Scratch/en/blog/2011-01-03-Happy-New-Year/index.html new file mode 100644 index 0000000..0dc8fa4 --- /dev/null +++ b/Scratch/en/blog/2011-01-03-Happy-New-Year/index.html @@ -0,0 +1,127 @@ + + + + + + YBlog - Happy New Year + + + + + + + + + + + + +
    + + +
    +

    Happy New Year

    +
    +
    +
    +
    +

    Happy New Year!

    +

    I was busy during the last months. But I will revive a bit this blog.

    +

    I made a project to write book in markdown syntax and generating HTML and high quality PDF. I am not finished with this.

    +

    I had written an efficient & simplistic MVC javascript framework.

    +

    Best wishes for 2011!

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-01-01 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/index.html b/Scratch/en/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/index.html new file mode 100644 index 0000000..35e47a4 --- /dev/null +++ b/Scratch/en/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/index.html @@ -0,0 +1,199 @@ + + + + + + YBlog - Why I won't use CoffeeScript (sadly) + + + + + + + + + + + + + +
    + + +
    +

    Why I won't use CoffeeScript (sadly)

    +
    +
    +
    +
    +

    Title image

    +
    + +

    Update: I might change my mind now. Why? I just discovered a js2coffee converter. Furthermore Denis Knauf told me about a CoffeeScript.eval function. And as Denis said: “it is time to use Coffeescript as a javascript with Ruby-like syntax not a Ruby-like programming language”.

    +
    + +
    + +

    tl;dr: I would have loved to program client side using a Ruby-like syntax. But in the end, CoffeScript raised more disavantages than advantages.

    +
    + +

    Recently I read this entry on HackerNews. The most upvoted comment praised (within other) CoffeeScript. Recently I used a lot of javascript. After trying Sproutcore, Cappuccino, looking at backbone.js & javascriptMVC, I’ve finally decided to make my own minimal javascript MVC framework.1

    +

    I had to fight the horrible syntax of javascript. It was like experiencing a back-in-time travel:

    +
      +
    • Verbose Java-like syntax,
    • +
    • Strange and insanely Verbose Object Oriented Programming,
    • +
    • No easy way to refer to current instance of a class (this doesn’t work really well),
    • +
    • etc…
    • +
    +

    It was so annoying at a point, I had thinked about creating my own CoffeeScript.

    +

    I’d finished a first draft of my MVC javascript framework. Just after I learned about the existence of CoffeeScript, I immediately created a new git branch to try it.

    +

    Here is my experience:

    +
      +
    1. I had to install node.js and use npm just to use CoffeeScript. It wasn’t a big deal but it wasn’t as straightfoward as I expected either.
    2. +
    3. Existing javascript file are not coffee compatible. I had to translate them by hand. There were no script to help me in this process. Thanks to vim, it wasn’t too hard to translate 90% of the javascript using some regexp. The --watch option of coffee was also really helpful to help in the translation. But I had to write my own shell script in order to follow an entire directory tree.
    4. +
    5. An unexpected event. I made some meta-programming in javascript using eval. But in order to work, the string in the eval must be written in pure javascript not in coffee. It was like writing in two different languages. Really not so good.
    6. +
    +

    Conclusion

    +

    Advantages:

    +
      +
    • Readability: clearly it resolved most of javascript syntax problems
    • +
    • Verbosity: I gained 14% line, 22% words, 14% characters
    • +
    +

    Disadvantages:

    +
      +
    • Added another compilation step to see how my code behave on the website.
    • +
    • I had to launch some script to generate on change every of my javascript file
    • +
    • I have to learn another Ruby-like language,
    • +
    • meta-programming become a poor experience,
    • +
    • I must convince people working with me to: +
        +
      • install node.js, npm and CoffeeScript,
      • +
      • remember to launch a script at each code session,
      • +
      • learn and use another ruby-like language
      • +
    • +
    +

    The last two point were definitively really problematic for me.

    +

    But even if I’ll have to work alone, I certainly won’t use CoffeeScript either. CoffeeScript is a third party and any of their update can break my code. I experienced this kind of situation many times, and it is very annoying. Far more than coding with a bad syntax.

    +

    Digression

    +

    I am sad. I wanted so much to program on Web Client with a Ruby-like syntax. But in the end I think it is not for me. I have to use the horrible javascript syntax for now. At least I would have preferred a complete ruby2js script for example2. But I believe it would be a really hard task just to simulate the access of current class for example.

    +

    Typically @x translate into this.x. But the following code will not do what I should expect. Call the foo function of the current class.

    +
    -> 
    +class MyClass
    +  foo: ->
    +    alert('ok')
    +
    +  bar: ->
    +    $('#content').load( '/content.html', ( -> @foo(x) ) )
    +    # That won't call MyClass.foo
    +

    The only way to handle this is to make the following code:

    +
    -> 
    +class MyClass
    +  foo: ->
    +    alert('ok')
    +
    +  bar: ->
    +    self=this
    +    $('#content').load( '/content.html', ( -> self.foo(x) ) )
    +

    Knowing this, @ notation lose most of its interrest for me.

    +
    +
    +
      +
    1. I know it may not be the best nor productive decision, but I’d like to start from scratch and understand how things works under the hood.

    2. +
    3. I know there is rb2js, but it doesn’t handle the problem I talk about.

    4. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-01-03 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/2011-04-20-Now-hosted-on-github/index.html b/Scratch/en/blog/2011-04-20-Now-hosted-on-github/index.html new file mode 100644 index 0000000..79a998f --- /dev/null +++ b/Scratch/en/blog/2011-04-20-Now-hosted-on-github/index.html @@ -0,0 +1,124 @@ + + + + + + YBlog - Now hosted on github + + + + + + + + + + + + +
    + + +
    +

    Now hosted on github

    +
    +
    +
    +
    +

    Title image

    +

    I am now hosted on github.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-04-20 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/A-more-convenient-diff/code/ydiff b/Scratch/en/blog/A-more-convenient-diff/code/ydiff new file mode 100644 index 0000000..90d6c85 --- /dev/null +++ b/Scratch/en/blog/A-more-convenient-diff/code/ydiff @@ -0,0 +1,22 @@ +#!/usr/bin/env zsh + +# Load colors helpers +autoload -U colors && colors + +function colorize_diff { + while read line; do + case ${line[0]} in + +) print -n $fg[green];; + -) print -n $fg[red];; + @) # Display in cyan the @@ positions @@ + if [[ ${line[1]} = '@' ]]; then + line=$(print $line | perl -pe 's#(\@\@[^\@]*\@\@)(.*)$#'$fg[cyan]'$1'$reset_color'$2#') + fi;; + + esac + print -- $line + print -n $reset_color + done +} + +diff -u $* | colorize_diff diff --git a/Scratch/en/blog/A-more-convenient-diff/index.html b/Scratch/en/blog/A-more-convenient-diff/index.html new file mode 100644 index 0000000..1d2f909 --- /dev/null +++ b/Scratch/en/blog/A-more-convenient-diff/index.html @@ -0,0 +1,148 @@ + + + + + + YBlog - A more convenient diff + + + + + + + + + + + + + +
    + + +
    +

    A more convenient diff

    +
    +
    +
    +
    +

    Diff is a very useful tool. But it is not so easy to read for us, simple mortal.

    +

    This is why, when you use git it will use a better formatting and colorize it.

    +

    Here is the script I use when I want to use human readable diff à la git.

    +
    #!/usr/bin/env zsh
    +
    +# Load colors helpers
    +autoload -U colors && colors
    +
    +function colorize_diff {
    +    while read line; do
    +    case ${line[0]} in
    +    +) print -n $fg[green];;
    +    -) print -n $fg[red];;
    +    @) # Display in cyan the @@ positions @@
    +       if [[ ${line[1]} = '@' ]]; then
    +           line=$(print $line | perl -pe 's#(\@\@[^\@]*\@\@)(.*)$#'$fg[cyan]'$1'$reset_color'$2#')
    +       fi;;
    +
    +    esac
    +        print -- $line
    +        print -n $reset_color
    +        done
    +}
    +
    +diff -u $* | colorize_diff
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-08-17 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/Category-Theory-Presentation/index.html b/Scratch/en/blog/Category-Theory-Presentation/index.html new file mode 100644 index 0000000..15e089d --- /dev/null +++ b/Scratch/en/blog/Category-Theory-Presentation/index.html @@ -0,0 +1,1090 @@ + + + + + + YBlog - Category Theory Presentation + + + + + + + + + + + + + +
    + + +
    +

    Category Theory Presentation

    +
    +
    +
    +
    + Cateogry of Hask's endofunctors + +

    Yesterday I was happy to make a presentation about Category Theory at Riviera Scala Clojure Meetup (note I used only Haskell for my examples).

    + + + +

    If you don't want to read them through an HTML presentations framework or downloading a big PDF +just continue to read as a standard web page. +

    + +
    +\(\newcommand{\F}{\mathbf{F}}\) +\(\newcommand{\E}{\mathbf{E}}\) +\(\newcommand{\C}{\mathcal{C}}\) +\(\newcommand{\D}{\mathcal{D}}\) +\(\newcommand{\id}{\mathrm{id}}\) +\(\newcommand{\ob}[1]{\mathrm{ob}(#1)}\) +\(\newcommand{\hom}[1]{\mathrm{hom}(#1)}\) +\(\newcommand{\Set}{\mathbf{Set}}\) +\(\newcommand{\Mon}{\mathbf{Mon}}\) +\(\newcommand{\Vec}{\mathbf{Vec}}\) +\(\newcommand{\Grp}{\mathbf{Grp}}\) +\(\newcommand{\Rng}{\mathbf{Rng}}\) +\(\newcommand{\ML}{\mathbf{ML}}\) +\(\newcommand{\Hask}{\mathbf{Hask}}\) +\(\newcommand{\Cat}{\mathbf{Cat}}\) +\(\newcommand{\fmap}{\mathtt{fmap}}\) +
    + +
    +

    Category Theory & Programming

    +
    for Rivieria Scala Clojure (Note this presentation uses Haskell)
    +by Yann Esposito +
    + + @yogsototh, + + + +yogsototh + +
    +
    +
    +

    Plan

    +
      +
    • General overview
    • +
    • Definitions
    • +
    • Applications
    • +
    +
    +
    +

    Not really about: Cat & glory

    +
    +Cat n glory
    credit to Tokuhiro Kawai (川井徳寛)
    +
    + +
    +
    +

    General Overview

    +
    +Samuel Eilenberg Saunders Mac Lane +
    + +

    Recent Math Field
    1942-45, Samuel Eilenberg & Saunders Mac Lane

    +

    Certainly one of the more abstract branches of math

    +
      +
    • New math foundation
      formalism abstraction, package entire theory
    • +
    • Bridge between disciplines
      Physics, Quantum Physics, Topology, Logic, Computer Science
    • +
    +

    +★: When is one thing equal to some other thing?, Barry Mazur, 2007
    ☆: Physics, Topology, Logic and Computation: A Rosetta Stone, John C. Baez, Mike Stay, 2009 +

    + +
    +
    +

    From a Programmer perspective

    +
    +

    Category Theory is a new language/framework for Math

    +
    +
      +
    • Another way of thinking
    • +
    • Extremely efficient for generalization
    • +
    +
    +
    +

    Math Programming relation

    +Buddha Fractal +

    Programming is doing Math

    +

    Strong relations between type theory and category theory.

    +

    Not convinced?
    Certainly a vocabulary problem.

    +

    One of the goal of Category Theory is to create a homogeneous vocabulary between different disciplines.

    +
    +
    +

    Vocabulary

    +mind blown +

    Math vocabulary used in this presentation:

    +
    +

    Category, Morphism, Associativity, Preorder, Functor, Endofunctor, Categorial property, Commutative diagram, Isomorph, Initial, Dual, Monoid, Natural transformation, Monad, Klesli arrows, κατα-morphism, ...

    +
    +
    +
    +

    Programmer Translation

    +lolcat + + + + + + + + +
    +Mathematician + +Programmer +
    +Morphism + +Arrow +
    +Monoid + +String-like +
    +Preorder + +Acyclic graph +
    +Isomorph + +The same +
    +Natural transformation + +rearrangement function +
    +Funny Category + +LOLCat +
    + +
    +
    +

    Plan

    +
      +
    • General overview
    • +
    • Definitions +
        +
      • Category
      • +
      • Intuition
      • +
      • Examples
      • +
      • Functor
      • +
      • Examples
      • +
      +
    • +
    • Applications
    • +
    +
    +
    +

    Category

    + +

    A way of representing things and ways to go between things.

    + +

    A Category \(\mathcal{C}\) is defined by:

    +
      +
    • Objects \(\ob{C}\),
    • +
    • Morphisms \(\hom{C}\),
    • +
    • a Composition law (∘)
    • +
    • obeying some Properties.
    • +
    +
    +
    +

    Category: Objects

    + +objects + +

    \(\ob{\mathcal{C}}\) is a collection

    +
    +
    +

    Category: Morphisms

    + +morphisms + +

    \(A\) and \(B\) objects of \(\C\)
    +\(\hom{A,B}\) is a collection of morphisms
    +\(f:A→B\) denote the fact \(f\) belongs to \(\hom{A,B}\)

    +

    \(\hom{\C}\) the collection of all morphisms of \(\C\)

    +
    +
    +

    Category: Composition

    +

    Composition (∘): associate to each couple \(f:A→B, g:B→C\) + $$g∘f:A\rightarrow C$$ +

    +composition +
    +
    +

    Category laws: neutral element

    +

    for each object \(X\), there is an \(\id_X:X→X\),
    +such that for each \(f:A→B\):

    +identity +
    +
    +

    Category laws: Associativity

    +

    Composition is associative:

    +associative composition +
    +
    +

    Commutative diagrams

    + +

    Two path with the same source and destination are equal.

    +
    + Commutative Diagram (Associativity) +
    + \((h∘g)∘f = h∘(g∘f) \) +
    +
    +
    + Commutative Diagram (Identity law) +
    + \(id_B∘f = f = f∘id_A \) +
    +
    +
    +
    +

    Question Time!

    + +
    + +
    +- French-only joke - +
    +
    +
    +
    +

    Can this be a category?

    +

    \(\ob{\C},\hom{\C}\) fixed, is there a valid ∘?

    +
    + Category example 1 +
    + YES +
    +
    +
    + Category example 2 +
    + no candidate for \(g∘f\) +
    NO +
    +
    +
    + Category example 3 +
    + YES +
    +
    +
    +
    +

    Can this be a category?

    +
    + Category example 4 +
    + no candidate for \(f:C→B\) +
    NO +
    +
    +
    + Category example 5 +
    + \((h∘g)∘f=\id_B∘f=f\)
    + \(h∘(g∘f)=h∘\id_A=h\)
    + but \(h≠f\)
    + NO +
    +
    +
    +
    +

    Categories Examples

    + +
    +Basket of cats +
    +- Basket of Cats - +
    +
    +
    +
    +

    Category \(\Set\)

    + +
      +
    • \(\ob{\Set}\) are all the sets
    • +
    • \(\hom{E,F}\) are all functions from \(E\) to \(F\)
    • +
    • ∘ is functions composition
    • +
    + +
      +
    • \(\ob{\Set}\) is a proper class ; not a set
    • +
    • \(\hom{E,F}\) is a set
    • +
    • \(\Set\) is then a locally small category
    • +
    +
    +
    +

    Categories Everywhere?

    +Cats everywhere +
      +
    • \(\Mon\): (monoids, monoid morphisms,∘)
    • +
    • \(\Vec\): (Vectorial spaces, linear functions,∘)
    • +
    • \(\Grp\): (groups, group morphisms,∘)
    • +
    • \(\Rng\): (rings, ring morphisms,∘)
    • +
    • Any deductive system T: (theorems, proofs, proof concatenation)
    • +
    • \( \Hask\): (Haskell types, functions, (.) )
    • +
    • ...
    • +
    +
    +
    +

    Smaller Examples

    + +

    Strings

    +Monoids are one object categories +
      +
    • \(\ob{Str}\) is a singleton
    • +
    • \(\hom{Str}\) each string
    • +
    • ∘ is concatenation (++)
    • +
    +
      +
    • "" ++ u = u = u ++ ""
    • +
    • (u ++ v) ++ w = u ++ (v ++ w)
    • +
    +
    +
    +

    Finite Example?

    + +

    Graph

    +
    +Each graph is a category +
    +
      +
    • \(\ob{G}\) are vertices
    • +
    • \(\hom{G}\) each path
    • +
    • ∘ is path concatenation
    • +
    +
    • \(\ob{G}=\{X,Y,Z\}\), +
    • \(\hom{G}=\{ε,α,β,γ,αβ,βγ,...\}\) +
    • \(αβ∘γ=αβγ\) +
    +
    +
    +

    Number construction

    + +

    Each Numbers as a whole category

    +Each number as a category +
    +
    +

    Degenerated Categories: Monoids

    + +Monoids are one object categories +

    Each Monoid \((M,e,⊙): \ob{M}=\{∙\},\hom{M}=M,\circ = ⊙\)

    +

    Only one object.

    +

    Examples:

    +
    • (Integer,0,+), (Integer,1,*), +
    • (Strings,"",++), for each a, ([a],[],++) +
    +
    +
    +

    Degenerated Categories: Preorders \((P,≤)\)

    + +
    • \(\ob{P}={P}\), +
    • \(\hom{x,y}=\{x≤y\} ⇔ x≤y\), +
    • \((y≤z) \circ (x≤y) = (x≤z) \) +
    + +

    At most one morphism between two objects.

    + +preorder category +
    +
    +

    Degenerated Categories: Discrete Categories

    + +Any set can be a category +

    Any Set

    +

    Any set \(E: \ob{E}=E, \hom{x,y}=\{x\} ⇔ x=y \)

    +

    Only identities

    +
    +
    +

    Choice

    +

    The same object can be seen in many different way as a category.

    +

    You can choose what are object, morphisms and composition.

    +

    ex: Str and discrete(Σ*)

    +
    +
    +

    Categorical Properties

    + +

    Any property which can be expressed in term of category, objects, morphism and composition.

    + +
    • Dual: \(\D\) is \(\C\) with reversed morphisms. +
    • Initial: \(Z\in\ob{\C}\) s.t. \(∀Y∈\ob{\C}, \#\hom{Z,Y}=1\) +
      Unique ("up to isormophism") +
    • Terminal: \(T\in\ob{\C}\) s.t. \(T\) is initial in the dual of \(\C\) +
    • Functor: structure preserving mapping between categories +
    • ... +
    +
    +
    +

    Isomorph

    +

    isomorph cats isomorphism: \(f:A→B\) which can be "undone" i.e.
    \(∃g:B→A\), \(g∘f=id_A\) & \(f∘g=id_B\)
    in this case, \(A\) & \(B\) are isomorphic.

    +

    A≌B means A and B are essentially the same.
    In Category Theory, = is in fact mostly .
    For example in commutative diagrams.

    +
    +
    +

    Functor

    + +

    A functor is a mapping between two categories. +Let \(\C\) and \(\D\) be two categories. +A functor \(\F\) from \(\C\) to \(\D\):

    +
      +
    • Associate objects: \(A\in\ob{\C}\) to \(\F(A)\in\ob{\D}\)
    • +
    • Associate morphisms: \(f:A\to B\) to \(\F(f) : \F(A) \to \F(B)\) + such that +
        +
      • \( \F (\)\(\id_X\)\()= \)\(\id\)\(\vphantom{\id}_{\F(}\)\(\vphantom{\id}_X\)\(\vphantom{\id}_{)} \),
      • +
      • \( \F (\)\(g∘f\)\()= \)\( \F(\)\(g\)\() \)\(\circ\)\( \F(\)\(f\)\() \)
      • +
      +
    • +
    +
    +
    +

    Functor Example (ob → ob)

    + +Functor +
    +
    +

    Functor Example (hom → hom)

    + +Functor +
    +
    +

    Functor Example

    + +Functor +
    +
    +

    Endofunctors

    + +

    An endofunctor for \(\C\) is a functor \(F:\C→\C\).

    +Endofunctor +
    +
    +

    Category of Categories

    + + + +

    Categories and functors form a category: \(\Cat\)

    +
    • \(\ob{\Cat}\) are categories +
    • \(\hom{\Cat}\) are functors +
    • ∘ is functor composition +
    +
    +
    +

    Plan

    +
      +
    • General overview
    • +
    • Definitions
    • +
    • Applications +
        +
      • \(\Hask\) category +
      • Functors +
      • Natural transformations +
      • Monads +
      • κατα-morphisms +
      +
    • +
    +
    +
    +

    Hask

    + +

    Category \(\Hask\):

    + +Haskell Category Representation + +
    • +\(\ob{\Hask} = \) Haskell types +
    • +\(\hom{\Hask} = \) Haskell functions +
    • +∘ = (.) Haskell function composition +
    + +

    Forget glitches because of undefined.

    +
    +
    +

    Haskell Kinds

    +

    In Haskell some types can take type variable(s). Typically: [a].

    +

    Types have kinds; The kind is to type what type is to function. Kind are the types for types (so meta).

    +
    Int, Char :: *
    +[], Maybe :: * -> *
    +(,), (->) :: * -> * -> *
    +[Int], Maybe Char, Maybe [Int] :: *
    +
    +
    +

    Haskell Types

    +

    Sometimes, the type determine a lot about the function:

    +
    fst :: (a,b) -> a -- Only one choice
    +snd :: (a,b) -> b -- Only one choice
    +f :: a -> [a]     -- Many choices
    +-- Possibilities: f x=[], or [x], or [x,x] or [x,...,x]
    +
    +? :: [a] -> [a] -- Many choices
    +-- can only rearrange: duplicate/remove/reorder elements
    +-- for example: the type of addOne isn't [a] -> [a]
    +addOne l = map (+1) l
    +-- The (+1) force 'a' to be a Num.
    + +

    +

    ★:Theorems for free!, Philip Wadler, 1989

    +
    +
    +

    Haskell Functor vs \(\Hask\) Functor

    + +

    A Haskell Functor is a type F :: * -> * which belong to the type class Functor ; thus instantiate +fmap :: (a -> b) -> (F a -> F b). + +

    & F: \(\ob{\Hask}→\ob{\Hask}\)
    & fmap: \(\hom{\Hask}→\hom{\Hask}\) + +

    The couple (F,fmap) is a \(\Hask\)'s functor if for any x :: F a:

    +
    • fmap id x = x +
    • fmap (f.g) x= (fmap f . fmap g) x +
    +
    +
    +

    Haskell Functors Example: Maybe

    + +
    data Maybe a = Just a | Nothing
    +instance Functor Maybe where
    +    fmap :: (a -> b) -> (Maybe a -> Maybe b)
    +    fmap f (Just a) = Just (f a)
    +    fmap f Nothing = Nothing
    +
    fmap (+1) (Just 1) == Just 2
    +fmap (+1) Nothing  == Nothing
    +fmap head (Just [1,2,3]) == Just 1
    +
    +
    +

    Haskell Functors Example: List

    + +
    instance Functor ([]) where
    +	fmap :: (a -> b) -> [a] -> [b]
    +	fmap = map
    +
    fmap (+1) [1,2,3]           == [2,3,4]
    +fmap (+1) []                == []
    +fmap head [[1,2,3],[4,5,6]] == [1,4]
    +
    +
    +

    Haskell Functors for the programmer

    +

    Functor is a type class used for types that can be mapped over.

    +
      +
    • Containers: [], Trees, Map, HashMap...
    • +
    • "Feature Type": +
        +
      • Maybe a: help to handle absence of a.
        Ex: safeDiv x 0 ⇒ Nothing
      • +
      • Either String a: help to handle errors
        Ex: reportDiv x 0 ⇒ Left "Division by 0!"
      • +
    • +
    +
    +
    +

    Haskell Functor intuition

    + +

    Put normal function inside a container. Ex: list, trees...

    + +Haskell Functor as a box play +

    +
    +

    Haskell Functor properties

    + +

    Haskell Functors are:

    + +
    • endofunctors ; \(F:\C→\C\) here \(\C = \Hask\), +
    • a couple (Object,Morphism) in \(\Hask\). +
    +
    +
    +

    Functor as boxes

    + +

    Haskell functor can be seen as boxes containing all Haskell types and functions. +Haskell types look like a fractal:

    + +Haskell functor representation +
    +
    +

    Functor as boxes

    + +

    Haskell functor can be seen as boxes containing all Haskell types and functions. +Haskell types look like a fractal:

    + +Haskell functor representation +
    +
    +

    Functor as boxes

    + +

    Haskell functor can be seen as boxes containing all Haskell types and functions. +Haskell types look like a fractal:

    + +Haskell functor representation +
    +
    +

    "Non Haskell" Hask's Functors

    +

    A simple basic example is the \(id_\Hask\) functor. It simply cannot be expressed as a couple (F,fmap) where

    +
      +
    • F::* -> *
    • +
    • fmap :: (a -> b) -> (F a) -> (F b)
    • +
    +

    Another example:

    +
      +
    • F(T)=Int
    • +
    • F(f)=\_->0
    • +
    +
    +
    +

    Also Functor inside \(\Hask\)

    +

    \(\mathtt{[a]}∈\ob{\Hask}\) but is also a category. Idem for Int.

    +

    length is a Functor from the category [a] to the category Int:

    +
      +
    • \(\ob{\mathtt{[a]}}=\{∙\}\)
    • +
    • \(\hom{\mathtt{[a]}}=\mathtt{[a]}\)
    • +
    • \(∘=\mathtt{(++)}\)
    • +
    +

    +
      +
    • \(\ob{\mathtt{Int}}=\{∙\}\)
    • +
    • \(\hom{\mathtt{Int}}=\mathtt{Int}\)
    • +
    • \(∘=\mathtt{(+)}\)
    • +
    +
    +
    • id: length [] = 0 +
    • comp: length (l ++ l') = (length l) + (length l') +
    +
    +
    +

    Category of \(\Hask\) Endofunctors

    +Category of Hask endofunctors +
    +
    +

    Category of Functors

    +

    If \(\C\) is small (\(\hom{\C}\) is a set). All functors from \(\C\) to some category \(\D\) form the category \(\mathrm{Func}(\C,\D)\).

    +
      +
    • \(\ob{\mathrm{Func}(\C,\D)}\): Functors \(F:\C→\D\)
    • +
    • \(\hom{\mathrm{Func}(\C,\D)}\): natural transformations
    • +
    • ∘: Functor composition
    • +
    +

    \(\mathrm{Func}(\C,\C)\) is the category of endofunctors of \(\C\).

    +
    +
    +

    Natural Transformations

    +

    Let \(F\) and \(G\) be two functors from \(\C\) to \(\D\).

    +

    Natural transformation commutative diagram A natural transformation: familly η ; \(η_X\in\hom{\D}\) for \(X\in\ob{\C}\) s.t.

    +

    ex: between Haskell functors; F a -> G a
    Rearragement functions only.

    +
    +
    +

    Natural Transformation Examples (1/4)

    +
    data List a = Nil | Cons a (List a)
    +toList :: [a] -> List a
    +toList [] = Nil
    +toList (x:xs) = Cons x (toList xs)
    +

    toList is a natural transformation. It is also a morphism from [] to List in the Category of \(\Hask\) endofunctors.

    +natural transformation commutative diagram +
    +natural transformation commutative diagram +
    + +
    +
    +

    Natural Transformation Examples (2/4)

    +
    data List a = Nil | Cons a (List a)
    +toHList :: List a -> [a]
    +toHList Nil = []
    +toHList (Cons x xs) = x:toHList xs
    +

    toHList is a natural transformation. It is also a morphism from List to [] in the Category of \(\Hask\) endofunctors.

    +natural transformation commutative diagram +
    +natural transformation commutative diagram
    toList . toHList = id & toHList . toList = id &
    therefore [] & List are isomorph.
    +
    + +
    +
    +

    Natural Transformation Examples (3/4)

    +
    toMaybe :: [a] -> Maybe a
    +toMaybe [] = Nothing
    +toMaybe (x:xs) = Just x
    +

    toMaybe is a natural transformation. It is also a morphism from [] to Maybe in the Category of \(\Hask\) endofunctors.

    +natural transformation commutative diagram +
    +natural transformation commutative diagram +
    + +
    +
    +

    Natural Transformation Examples (4/4)

    +
    mToList :: Maybe a -> [a]
    +mToList Nothing = []
    +mToList Just x  = [x]
    +

    toMaybe is a natural transformation. It is also a morphism from [] to Maybe in the Category of \(\Hask\) endofunctors.

    +natural transformation commutative diagram +
    +relation between [] and Maybe
    There is no isomorphism.
    Hint: Bool lists longer than 1.
    +
    + +
    +
    +

    Composition problem

    +

    The Problem; example with lists:

    +
    f x = [x]       ⇒ f 1 = [1]   ⇒ (f.f) 1 = [[1]] ✗
    +g x = [x+1]     ⇒ g 1 = [2]   ⇒ (g.g) 1 = ERROR [2]+1 ✗
    +h x = [x+1,x*3] ⇒ h 1 = [2,3] ⇒ (h.h) 1 = ERROR [2,3]+1 ✗ 
    + +

    The same problem with most f :: a -> F a functions and functor F.

    +
    +
    +

    Composition Fixable?

    +

    How to fix that? We want to construct an operator which is able to compose:

    +

    f :: a -> F b & g :: b -> F c.

    +

    More specifically we want to create an operator ◎ of type

    +

    ◎ :: (b -> F c) -> (a -> F b) -> (a -> F c)

    +

    Note: if F = I, ◎ = (.).

    +
    +
    +

    Fix Composition (1/2)

    +

    Goal, find: ◎ :: (b -> F c) -> (a -> F b) -> (a -> F c)
    f :: a -> F b, g :: b -> F c:

    +
      +
    • (g ◎ f) x ???
    • +
    • First apply f to xf x :: F b
    • +
    • Then how to apply g properly to an element of type F b?
    • +
    +
    +
    +

    Fix Composition (2/2)

    +

    Goal, find: ◎ :: (b -> F c) -> (a -> F b) -> (a -> F c)
    f :: a -> F b, g :: b -> F c, f x :: F b:

    +
      +
    • Use fmap :: (t -> u) -> (F t -> F u)!
    • +
    • (fmap g) :: F b -> F (F c) ; (t=b, u=F c)
    • +
    • (fmap g) (f x) :: F (F c) it almost WORKS!
    • +
    • We lack an important component, join :: F (F c) -> F c
    • +
    • (g ◎ f) x = join ((fmap g) (f x))
      ◎ is the Kleisli composition; in Haskell: <=< (in Control.Monad).
    • +
    +
    +
    +

    Necessary laws

    +

    For ◎ to work like composition, we need join to hold the following properties:

    +
      +
    • join (join (F (F (F a))))=join (F (join (F (F a))))
    • +
    • abusing notations denoting join by ⊙; this is equivalent to
      (F ⊙ F) ⊙ F = F ⊙ (F ⊙ F)
    • +
    • There exists η :: a -> F a s.t.
      η⊙F=F=F⊙η
    • +
    +
    +
    +

    Klesli composition

    +

    Now the composition works as expected. In Haskell ◎ is <=< in Control.Monad.

    +

    g <=< f = \x -> join ((fmap g) (f x))

    +
    f x = [x]       ⇒ f 1 = [1]   ⇒ (f <=< f) 1 = [1] ✓
    +g x = [x+1]     ⇒ g 1 = [2]   ⇒ (g <=< g) 1 = [3] ✓
    +h x = [x+1,x*3] ⇒ h 1 = [2,3] ⇒ (h <=< h) 1 = [3,6,4,9] ✓
    + +
    +
    +

    We reinvented Monads!

    +

    A monad is a triplet (M,⊙,η) where

    +
      +
    • \(M\) an Endofunctor (to type a associate M a)
    • +
    • \(⊙:M×M→M\) a nat. trans. (i.e. ⊙::M (M a) → M a ; join)
    • +
    • \(η:I→M\) a nat. trans. (\(I\) identity functor ; η::a → M a)
    • +
    +

    Satisfying

    +
      +
    • \(M ⊙ (M ⊙ M) = (M ⊙ M) ⊙ M\)
    • +
    • \(η ⊙ M = M = M ⊙ η\)
    • +
    +
    +
    +

    Compare with Monoid

    +

    A Monoid is a triplet \((E,∙,e)\) s.t.

    +
      +
    • \(E\) a set
    • +
    • \(∙:E×E→E\)
    • +
    • \(e:1→E\)
    • +
    +

    Satisfying

    +
      +
    • \(x∙(y∙z) = (x∙y)∙z, ∀x,y,z∈E\)
    • +
    • \(e∙x = x = x∙e, ∀x∈E\)
    • +
    +
    +
    +

    Monads are just Monoids

    +
    +

    A Monad is just a monoid in the category of endofunctors, what's the problem?

    +
    +

    The real sentence was:

    +
    +

    All told, a monad in X is just a monoid in the category of endofunctors of X, with product × replaced by composition of endofunctors and unit set by the identity endofunctor.

    +
    +
    +
    +

    Example: List

    +
      +
    • [] :: * -> * an Endofunctor
    • +
    • \(⊙:M×M→M\) a nat. trans. (join :: M (M a) -> M a)
    • +
    • \(η:I→M\) a nat. trans.
    • +
    +
    -- In Haskell ⊙ is "join" in "Control.Monad"
    +join :: [[a]] -> [a]
    +join = concat
    +
    +-- In Haskell the "return" function (unfortunate name)
    +η :: a -> [a]
    +η x = [x]
    + +
    +
    +

    Example: List (law verification)

    +

    Example: List is a functor (join is ⊙)

    +
      +
    • \(M ⊙ (M ⊙ M) = (M ⊙ M) ⊙ M\)
    • +
    • \(η ⊙ M = M = M ⊙ η\)
    • +
    +
    join [ join [[x,y,...,z]] ] = join [[x,y,...,z]]
    +                            = join (join [[[x,y,...,z]]])
    +join (η [x]) = [x] = join [η x]
    + +

    Therefore ([],join,η) is a monad.

    +
    +
    +

    Monads useful?

    +

    A LOT of monad tutorial on the net. Just one example; the State Monad

    +

    DrawScene to State Screen DrawScene ; still pure.

    +
    main = drawImage (width,height)
    +
    +drawImage :: Screen -> DrawScene
    +drawImage screen = do
    +    drawPoint p screen
    +    drawCircle c screen
    +    drawRectangle r screen
    +
    +drawPoint point screen = ...
    +drawCircle circle screen = ...
    +drawRectangle rectangle screen = ...
    +
    main = do
    +    put (Screen 1024 768)
    +    drawImage
    +
    +drawImage :: State Screen DrawScene
    +drawImage = do
    +    drawPoint p
    +    drawCircle c
    +    drawRectangle r
    +
    +drawPoint :: Point ->
    +               State Screen DrawScene
    +drawPoint p = do
    +    Screen width height <- get
    +    ...
    +
    +
    +

    fold

    +fold +
    +
    +

    κατα-morphism

    +catamorphism +
    +
    +

    κατα-morphism: fold generalization

    +

    acc type of the "accumulator":
    fold :: (acc -> a -> acc) -> acc -> [a] -> acc

    +

    Idea: put the accumulated value inside the type.

    +
    -- Equivalent to fold (+1) 0 "cata"
    +(Cons 'c' (Cons 'a' (Cons 't' (Cons 'a' Nil))))
    +(Cons 'c' (Cons 'a' (Cons 't' (Cons 'a' 0))))
    +(Cons 'c' (Cons 'a' (Cons 't' 1)))
    +(Cons 'c' (Cons 'a' 2))
    +(Cons 'c' 3)
    +4
    + +

    But where are all the informations? (+1) and 0?

    +
    +
    +

    κατα-morphism: Missing Information

    +

    Where is the missing information?

    +
      +
    • Functor operator fmap
    • +
    • Algebra representing the (+1) and also knowing about the 0.
    • +
    +

    First example, make length on [Char]

    +
    +
    +

    κατα-morphism: Type work

    +
    
    +data StrF a = Cons Char a | Nil
    +data Str' = StrF Str'
    +
    +-- generalize the construction of Str to other datatype
    +-- Mu: type fixed point
    +-- Mu :: (* -> *) -> *
    +
    +data Mu f = InF { outF :: f (Mu f) }
    +data Str = Mu StrF
    +
    +-- Example
    +foo=InF { outF = Cons 'f'
    +        (InF { outF = Cons 'o'
    +            (InF { outF = Cons 'o'
    +                (InF { outF = Nil })})})}
    + +
    +
    +

    κατα-morphism: missing information retrieved

    +
    type Algebra f a = f a -> a
    +instance Functor (StrF a) =
    +    fmap f (Cons c x) = Cons c (f x)
    +    fmap _ Nil = Nil
    + +
    cata :: Functor f => Algebra f a -> Mu f -> a
    +cata f = f . fmap (cata f) . outF
    + +
    +
    +

    κατα-morphism: Finally length

    +

    All needed information for making length.

    +
    instance Functor (StrF a) =
    +    fmap f (Cons c x) = Cons c (f x)
    +    fmap _ Nil = Nil
    +
    +length' :: Str -> Int
    +length' = cata phi where
    +    phi :: Algebra StrF Int -- StrF Int -> Int
    +    phi (Cons a b) = 1 + b
    +    phi Nil = 0
    +
    +main = do
    +    l <- length' $ stringToStr "Toto"
    +    ...
    +
    +
    +

    κατα-morphism: extension to Trees

    +

    Once you get the trick, it is easy to extent to most Functor.

    +
    type Tree = Mu TreeF
    +data TreeF x = Node Int [x]
    +
    +instance Functor TreeF where
    +  fmap f (Node e xs) = Node e (fmap f xs)
    +
    +depth = cata phi where
    +  phi :: Algebra TreeF Int -- TreeF Int -> Int
    +  phi (Node x sons) = 1 + foldr max 0 sons
    +
    +
    +

    Conclusion

    +

    Category Theory oriented Programming:

    +
      +
    • Focus on the type and operators
    • +
    • Extreme generalisation
    • +
    • Better modularity
    • +
    • Better control through properties of types
    • +
    +

    No cat were harmed in the making of this presentation.

    +
    + +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2012-12-12 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/Haskell-Mandelbrot/code/animandel.hs b/Scratch/en/blog/Haskell-Mandelbrot/code/animandel.hs new file mode 100644 index 0000000..6a81748 --- /dev/null +++ b/Scratch/en/blog/Haskell-Mandelbrot/code/animandel.hs @@ -0,0 +1,8 @@ +a=27;b=79;c=C(-2.0,-1.0);d=C(1.0,1.0);e=C(-2.501,-1.003) +newtype C = C (Double,Double) deriving (Show,Eq) +instance Num C where C(x,y)*C(z,t)=C(z*x-y*t,y*z+x*t);C(x,y)+C(z,t)=C(x+z,y+t);abs(C(x,y))=C(sqrt(x*x+y*y),0.0) +r(C(x,y))=x;i(C(x,y))=y +f c z 0=0;f c z n=if(r(abs(z))>2)then n else f c ((z*z)+c) (n-1) +h j k = map (\z->(f (C z) (C(0,0)) 32,(fst z>l - q/2))) [(x,y)|y<-[p,(p+((o-p)/a))..o],x<-[m,(m + q)..l]] where o=i k;p=i j;m=r j;l=r k;q=(l-m)/b +u j k = concat $ map v $ h j k where v (i,p)=(" .,`'°\":;-+oO0123456789=!%*§&$@#"!!i):rst p;rst True="\n";rst False="" +main = putStrLn $ im 0 where cl n (C (x,y))=let cs=(1.1**n-1) in C ((x+cs*(r e))/cs+1,(y+cs*(i e))/cs+1);bl n=cl n c;tr n=cl n d;im n=u (bl n) (tr n)++"\x1b[H\x1b[25A"++im (n+1) diff --git a/Scratch/en/blog/Haskell-Mandelbrot/index.html b/Scratch/en/blog/Haskell-Mandelbrot/index.html new file mode 100644 index 0000000..fe5d57a --- /dev/null +++ b/Scratch/en/blog/Haskell-Mandelbrot/index.html @@ -0,0 +1,224 @@ + + + + + + YBlog - ASCII Haskell Mandelbrot + + + + + + + + + + + + + +
    + + +
    +

    ASCII Haskell Mandelbrot

    +
    +
    +
    +
    +

    Here is the obfuscated code:

    +
    a=27;b=79;c=C(-2.0,-1.0);d=C(1.0,1.0);e=C(-2.501,-1.003)
    +newtype C = C (Double,Double) deriving (Show,Eq)
    +instance Num C where C(x,y)*C(z,t)=C(z*x-y*t,y*z+x*t);C(x,y)+C(z,t)=C(x+z,y+t);abs(C(x,y))=C(sqrt(x*x+y*y),0.0)
    +r(C(x,y))=x;i(C(x,y))=y
    +f c z 0=0;f c z n=if(r(abs(z))>2)then n else f c ((z*z)+c) (n-1)
    +h j k = map (\z->(f (C z) (C(0,0)) 32,(fst z>l - q/2))) [(x,y)|y<-[p,(p+((o-p)/a))..o],x<-[m,(m + q)..l]] where o=i k;p=i j;m=r j;l=r k;q=(l-m)/b
    +u j k = concat $ map v $ h j k where v (i,p)=(" .,`'°\":;-+oO0123456789=!%*§&$@#"!!i):rst p;rst True="\n";rst False=""
    +main = putStrLn $ im 0 where cl n (C (x,y))=let cs=(1.1**n-1) in C ((x+cs*(r e))/cs+1,(y+cs*(i e))/cs+1);bl n=cl n c;tr n=cl n d;im n=u (bl n) (tr n)++"\x1b[H\x1b[25A"++im (n+1)
    +

    To launch it, you’ll need to have haskell installed and to run:

    +

    ghc –make animandel.hs && animandel

    +

    Here is some image after 50 iterations:

    +
    +###@@@@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&&&&WWOOClbUOWW&&$$$$$$$$$$$$$$
    +##@@@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&&&&WWUCUb; ,jUOWW&&&$$$$$$$$$$$$
    +#@@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&&WWWWWUb       ooCWW&&&&&&$$$$$$$$
    +@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&WWWWWWWWOU         uUOWWWW&&&&&&$$$$$
    +@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&&WOUObUOOOUUUCbi      rbCUUUOWWWWWOUW&$$$
    +@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&&&&&WWWUcr,iiCb                o wUUUUUC;OW&$$
    +$$$$$$$$$$$$$$$$$$$$$$$$$$&&&&&&&&&&WWWWOUC,                         j    llW&&$
    +$$$$$$$$$$$$$$$$$$$$$&&&&&&&&&&&&WWWWWWOCCbi                              bWWW&&
    +$$$$$$$$$$$$$$$$$&&WWWWWWW&&&WWWWWWWWOUo                                 jUOWW&&
    +$$$$$$$$$$$$$$&&&WWOwOOWWWOUUOWWWWWOOUbw                                  j.blW&
    +$$$$$$$$$$$&&&&&WWWObiijbUCl bCiUUUUUCj,                                    bOW&
    +$$$$$$$$$&&&&&&&WWWOUbw  ;      oobCbl                                     jUWW&
    +$$$$$$$&&&&&&&WWWWOcbi             ij                                      jUW&&
    +$$$$$&&WWWWWWWOwUUCbw                                                       WW&&
    +WWWOWWWWWWWWWUUbo                                                         UWWW&&
    +:                                                                      wbUOWW&&&
    +WWWOWWWWWWWWWUUbo                                                         UWWW&&
    +$$$$$&&WWWWWWWOwUUCbw                                                       WW&&
    +$$$$$$$&&&&&&&WWWWOcbi             ij                                      jUW&&
    +$$$$$$$$$&&&&&&&WWWOUbw  ;      oobCbl                                     jUWW&
    +$$$$$$$$$$$&&&&&WWWObiijbUCl bCiUUUUUCj,                                    bOW&
    +$$$$$$$$$$$$$$&&&WWOwOOWWWOUUOWWWWWOOUbw                                  j.blW&
    +$$$$$$$$$$$$$$$$$&&WWWWWWW&&&WWWWWWWWOUo                                 jUOWW&&
    +$$$$$$$$$$$$$$$$$$$$$&&&&&&&&&&&&WWWWWWOCCbi                              bWWW&&
    +$$$$$$$$$$$$$$$$$$$$$$$$$$&&&&&&&&&&WWWWOUC,                         j    llW&&$
    +@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&&&&&WWWUcr,iiCb                o wUUUUUC;OW&$$
    +
    + +

    Here is the more readable version. I believe with this far more readable version, no more explanation is needed.

    +
    nbvert = 30
    +nbhor = 79
    +zoomfactor = 1.01
    +init_bottom_left = C (-2.0,-2.0)
    +init_top_right   = C (3.0,2.0)
    +interrest        = C (-1.713,-0.000)
    +
    +newtype Complex = C (Float,Float) deriving (Show,Eq)
    +instance Num Complex where
    +    fromInteger n     = C (fromIntegral n,0.0)
    +    C (x,y) * C (z,t) = C (z*x - y*t, y*z + x*t)
    +    C (x,y) + C (z,t) = C (x+z, y+t)
    +    abs (C (x,y))     = C (sqrt (x*x + y*y),0.0)
    +    signum (C (x,y))  = C (signum x , 0.0)
    +
    +real :: Complex -> Float
    +real (C (x,y))    = x
    +im :: Complex -> Float
    +im   (C (x,y))    = y
    +
    +cabs :: Complex -> Float
    +cabs = real.abs
    +
    +f :: Complex -> Complex -> Int -> Int
    +f c z 0 = 0
    +f c z n = if (cabs z > 2) then n else f c ((z*z)+c) (n-1) 
    +
    +bmandel bottomleft topright = map (\z -> (f (C z) (C(0,0)) 32, (fst z > right - hstep/2 ))) [(x,y) | y <- [bottom,(bottom + vstep)..top], x<-[left,(left + hstep)..right]]
    +    where
    +        top = im topright
    +        bottom = im bottomleft
    +        left = real bottomleft
    +        right = real topright
    +        vstep=(top-bottom)/nbvert
    +        hstep=(right-left)/nbhor
    +
    +mandel :: (Complex,Complex) -> String
    +mandel (bottomleft,topright) = concat $ map treat $ bmandel bottomleft topright
    +    where
    +        treat (i,jump) = " .,:;rcuowijlbCUOW&$@#" !! (div (i*22) 32):rst jump
    +        rst True = "\n"
    +        rst False = ""
    +
    +cdiv :: Complex -> Float -> Complex
    +cdiv (C(x,y)) r = C(x/r, y/r) 
    +cmul :: Complex -> Float -> Complex
    +cmul (C(x,y)) r = C(x*r, y*r) 
    +
    +zoom :: Complex -> Complex -> Complex -> Float -> (Complex,Complex)
    +zoom bl tr center magn = (f bl, f tr)
    +    where
    +        f point = ((center `cmul` magn) + point ) `cdiv` (magn + 1)
    +
    +main = do
    +    x <- getContents
    +    putStrLn $ infinitemandel 0
    +    where
    +        window n = zoom init_bottom_left init_top_right interrest (zoomfactor**n) 
    +        infinitemandel n = mandel (window n) ++ "\x1b[H\x1b[25A" ++ infinitemandel (n+1)
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-07-10 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/00_Introduction.lhs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/00_Introduction.lhs new file mode 100644 index 0000000..1d9df7d --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/00_Introduction.lhs @@ -0,0 +1,53 @@ + ## Introduction + +In my +[preceding article](/Scratch/en/blog/Haskell-the-Hard-Way/) I introduced Haskell. + +This article goes further. +It will show how to use functional programming with interactive programs. +But more than that, it will show how to organize your code in a functional way. +This article is more about functional paradigm than functional language. +The code organization can be used in most imperative language. + +As Haskell is designed for functional paradigm, it is easier to use in this context. +In reality, the firsts sections will use an imperative paradigm. +As you can use functional paradigm in imperative language, +you can also use imperative paradigm in functional languages. + +This article is about creating an useful and clean program. +It can interact with the user in real time. +It uses OpenGL, a library with imperative programming foundations. +Despite this fact, +most of the final code will remain in the pure part (no `IO`). + +I believe the main audience for this article are: + +- Haskell programmer looking for an OpengGL tutorial. +- People interested in program organization (programming language agnostic). +- Fractal lovers and in particular 3D fractal. +- People interested in user interaction in a functional paradigm. + +I had in mind for some time now to make a Mandelbrot set explorer. +I had already written a [command line Mandelbrot set generator in Haskell](http://github.com/yogsototh/mandelbrot.git). +This utility is highly parallel; it uses the `repa` package[^001]. + +[^001]: Unfortunately, I couldn't make this program to work on my Mac. More precisely, I couldn't make the [DevIL](http://openil.sourceforge.net/) library work on Mac to output the image. Yes I have done a `brew install libdevil`. But even a minimal program who simply write some `jpg` didn't worked. I tried both with `Haskell` and `C`. + +This time, we will not parallelize the computation. +Instead, we will display the Mandelbrot set extended in 3D using OpenGL and Haskell. +You will be able to move it using your keyboard. +This object is a Mandelbrot set in the plan (z=0), +and something nice to see in 3D. + +Here are some screenshots of the result: + +blogfigure("GoldenMandelbulb.png","The entire Mandelbulb") +blogfigure("3DMandelbulbDetail.png","A Mandelbulb detail") +blogfigure("3DMandelbulbDetail2.png","Another detail of the Mandelbulb") + +And you can see the intermediate steps to reach this goal: + +blogimage("HGL_Plan.png","The parts of the article") + +From the 2nd section to the 4th it will be _dirtier_ and _dirtier_. +We start cleaning the code at the 5th section. diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/01_Introduction/hglmandel.lhs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/01_Introduction/hglmandel.lhs new file mode 100644 index 0000000..954c6f8 --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/01_Introduction/hglmandel.lhs @@ -0,0 +1,182 @@ + ## First version + +We can consider two parts. +The first being mostly some boilerplate[^011]. +And the second part more focused on OpenGL and content. + +[^011]: Generally in Haskell you need to declare a lot of import lines. + This is something I find annoying. + In particular, it should be possible to create a special file, Import.hs + which make all the necessary import for you, as you generally need them all. + I understand why this is cleaner to force the programmer not to do so, + but, each time I do a copy/paste, I feel something is wrong. + I believe this concern can be generalized to the lack of namespace in Haskell. + + ### Let's play the song of our people + +> import Graphics.Rendering.OpenGL +> import Graphics.UI.GLUT +> import Data.IORef + +For efficiency reason[^010001], I will not use the default Haskell `Complex` data type. + +[^010001]: I tried `Complex Double`, `Complex Float`, this current data type with `Double` and the actual version `Float`. For rendering a 1024x1024 Mandelbrot set it takes `Complex Double` about 6.8s, for `Complex Float` about 5.1s, for the actual version with `Double` and `Float` it takes about `1.6` sec. See these sources for testing yourself: [https://gist.github.com/2945043](https://gist.github.com/2945043). If you really want to things to go faster, use `data Complex = C {-# UNPACK #-} !Float {-# UNPACK #-} !Float`. It takes only one second instead of 1.6s. + +> data Complex = C (Float,Float) deriving (Show,Eq) + + +> instance Num Complex where +> fromInteger n = C (fromIntegral n,0.0) +> C (x,y) * C (z,t) = C (z*x - y*t, y*z + x*t) +> C (x,y) + C (z,t) = C (x+z, y+t) +> abs (C (x,y)) = C (sqrt (x*x + y*y),0.0) +> signum (C (x,y)) = C (signum x , 0.0) + +We declare some useful functions for manipulating complex numbers: + +> complex :: Float -> Float -> Complex +> complex x y = C (x,y) +> +> real :: Complex -> Float +> real (C (x,y)) = x +> +> im :: Complex -> Float +> im (C (x,y)) = y +> +> magnitude :: Complex -> Float +> magnitude = real.abs + + + ### Let us start + +We start by giving the main architecture of our program: + +> main :: IO () +> main = do +> -- GLUT need to be initialized +> (progname,_) <- getArgsAndInitialize +> -- We will use the double buffered mode (GL constraint) +> initialDisplayMode $= [DoubleBuffered] +> -- We create a window with some title +> createWindow "Mandelbrot Set with Haskell and OpenGL" +> -- Each time we will need to update the display +> -- we will call the function 'display' +> displayCallback $= display +> -- We enter the main loop +> mainLoop + +Mainly, we initialize our OpenGL application. +We declared that the function `display` will be used to render the graphics: + +> display = do +> clear [ColorBuffer] -- make the window black +> loadIdentity -- reset any transformation +> preservingMatrix drawMandelbrot +> swapBuffers -- refresh screen + +Also here, there is only one interesting line; +the draw will occur in the function `drawMandelbrot`. + +This function will provide a list of draw actions. +Remember that OpenGL is imperative by design. +Then, one of the consequence is you must write the actions in the right order. +No easy parallel drawing here. +Here is the function which will render something on the screen: + +> drawMandelbrot = +> -- We will print Points (not triangles for example) +> renderPrimitive Points $ do +> mapM_ drawColoredPoint allPoints +> where +> drawColoredPoint (x,y,c) = do +> color c -- set the current color to c +> -- then draw the point at position (x,y,0) +> -- remember we're in 3D +> vertex $ Vertex3 x y 0 + +The `mapM_` function is mainly the same as map but inside a monadic context. +More precisely, this can be transformed as a list of actions where the order is important: + +~~~ +drawMandelbrot = + renderPrimitive Points $ do + color color1 + vertex $ Vertex3 x1 y1 0 + ... + color colorN + vertex $ Vertex3 xN yN 0 +~~~ + +We also need some kind of global variables. +In fact, global variable are a proof of a design problem. +We will get rid of them later. + +> width = 320 :: GLfloat +> height = 320 :: GLfloat + +And of course our list of colored points. +In OpenGL the default coordinate are from -1 to 1. + +> allPoints :: [(GLfloat,GLfloat,Color3 GLfloat)] +> allPoints = [ (x/width,y/height,colorFromValue $ mandel x y) | +> x <- [-width..width], +> y <- [-height..height]] +> + +We need a function which transform an integer value to some color: + +> colorFromValue n = +> let +> t :: Int -> GLfloat +> t i = 0.5 + 0.5*cos( fromIntegral i / 10 ) +> in +> Color3 (t n) (t (n+5)) (t (n+10)) + +And now the `mandel` function. +Given two coordinates in pixels, it returns some integer value: + +> mandel x y = +> let r = 2.0 * x / width +> i = 2.0 * y / height +> in +> f (complex r i) 0 64 + +It uses the main Mandelbrot function for each complex \\(c\\). +The Mandelbrot set is the set of complex number \\(c\\) such that the following sequence does not escape to infinity. + +Let us define \\(f_c: \mathbb{C} \to \mathbb{C}\\) + +$$ f_c(z) = z^2 + c $$ + +The sequence is: + +$$ 0 \rightarrow f_c(0) \rightarrow f_c(f_c(0)) \rightarrow \cdots \rightarrow f^n_c(0) \rightarrow \cdots $$ + +Of course, instead of trying to test the real limit, we just make a test after a finite number of occurrences. + +> f :: Complex -> Complex -> Int -> Int +> f c z 0 = 0 +> f c z n = if (magnitude z > 2 ) +> then n +> else f c ((z*z)+c) (n-1) + +Well, if you download this file (look at the bottom of this section), compile it and run it this is the result: + +blogimage("hglmandel_v01.png","The mandelbrot set version 1") + +A first very interesting property of this program is that the computation for all the points is done only once. +It is a bit long before the first image appears, but if you resize the window, it updates instantaneously. +This property is a direct consequence of purity. +If you look closely, you see that `allPoints` is a pure list. +Therefore, calling `allPoints` will always render the same result and Haskell is clever enough to use this property. +While Haskell doesn't garbage collect `allPoints` the result is reused for free. +We did not specified this value should be saved for later use. +It is saved for us. + +See what occurs if we make the window bigger: + +blogimage("hglmandel_v01_too_wide.png","The mandelbrot too wide, black lines and columns") + +We see some black lines because we have drawn less point than there is on the surface. +We can repair this by drawing little squares instead of just points. +But, instead we will do something a bit different and unusual. diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/02_Edges/HGLMandelEdge.lhs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/02_Edges/HGLMandelEdge.lhs new file mode 100644 index 0000000..715db44 --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/02_Edges/HGLMandelEdge.lhs @@ -0,0 +1,152 @@ + ## Only the edges + +
    + +> import Graphics.Rendering.OpenGL +> import Graphics.UI.GLUT +> import Data.IORef +> -- Use UNPACK data because it is faster +> -- The ! is for strict instead of lazy +> data Complex = C {-# UNPACK #-} !Float +> {-# UNPACK #-} !Float +> deriving (Show,Eq) +> instance Num Complex where +> fromInteger n = C (fromIntegral n) 0.0 +> (C x y) * (C z t) = C (z*x - y*t) (y*z + x*t) +> (C x y) + (C z t) = C (x+z) (y+t) +> abs (C x y) = C (sqrt (x*x + y*y)) 0.0 +> signum (C x y) = C (signum x) 0.0 +> complex :: Float -> Float -> Complex +> complex x y = C x y +> +> real :: Complex -> Float +> real (C x y) = x +> +> im :: Complex -> Float +> im (C x y) = y +> +> magnitude :: Complex -> Float +> magnitude = real.abs +> main :: IO () +> main = do +> -- GLUT need to be initialized +> (progname,_) <- getArgsAndInitialize +> -- We will use the double buffered mode (GL constraint) +> initialDisplayMode $= [DoubleBuffered] +> -- We create a window with some title +> createWindow "Mandelbrot Set with Haskell and OpenGL" +> -- Each time we will need to update the display +> -- we will call the function 'display' +> displayCallback $= display +> -- We enter the main loop +> mainLoop +> display = do +> -- set the background color (dark solarized theme) +> clearColor $= Color4 0 0.1686 0.2117 1 +> clear [ColorBuffer] -- make the window black +> loadIdentity -- reset any transformation +> preservingMatrix drawMandelbrot +> swapBuffers -- refresh screen +> +> width = 320 :: GLfloat +> height = 320 :: GLfloat + + +
    + +This time, instead of drawing all points, +we will simply draw the edges of the Mandelbrot set. +The method I use is a rough approximation. +I consider the Mandelbrot set to be almost convex. +The result will be good enough for the purpose of this tutorial. + +We change slightly the `drawMandelbrot` function. +We replace the `Points` by `LineLoop` + +> drawMandelbrot = +> -- We will print Points (not triangles for example) +> renderPrimitive LineLoop $ do +> mapM_ drawColoredPoint allPoints +> where +> drawColoredPoint (x,y,c) = do +> color c -- set the current color to c +> -- then draw the point at position (x,y,0) +> -- remember we're in 3D +> vertex $ Vertex3 x y 0 + +And now, we should change our list of points. +Instead of drawing every point of the visible surface, +we will choose only point on the surface. + +> allPoints = positivePoints ++ +> map (\(x,y,c) -> (x,-y,c)) (reverse positivePoints) + +We only need to compute the positive point. +The Mandelbrot set is symmetric relatively to the abscisse axis. + +> positivePoints :: [(GLfloat,GLfloat,Color3 GLfloat)] +> positivePoints = do +> x <- [-width..width] +> let y = maxZeroIndex (mandel x) 0 height (log2 height) +> if y < 1 -- We don't draw point in the absciss +> then [] +> else return (x/width,y/height,colorFromValue $ mandel x y) +> where +> log2 n = floor ((log n) / log 2) + +This function is interesting. +For those not used to the list monad here is a natural language version of this function: + + +positivePoints = + for all x in the range [-width..width] + let y be smallest number s.t. mandel x y > 0 + if y is on 0 then don't return a point + else return the value corresonding to (x,y,color for (x+iy)) + + +In fact using the list monad you write like if you consider only one element at a time and the computation is done non deterministically. +To find the smallest number such that `mandel x y > 0` we use a simple dichotomy: + +> -- given f min max nbtest, +> -- considering +> -- - f is an increasing function +> -- - f(min)=0 +> -- - f(max)≠0 +> -- then maxZeroIndex f min max nbtest returns x such that +> -- f(x - ε)=0 and f(x + ε)≠0 +> -- where ε=(max-min)/2^(nbtest+1) +> maxZeroIndex func minval maxval 0 = (minval+maxval)/2 +> maxZeroIndex func minval maxval n = +> if (func medpoint) /= 0 +> then maxZeroIndex func minval medpoint (n-1) +> else maxZeroIndex func medpoint maxval (n-1) +> where medpoint = (minval+maxval)/2 + +No rocket science here. See the result now: + +blogimage("HGLMandelEdges.png","The edges of the mandelbrot set") + +
    + +> colorFromValue n = +> let +> t :: Int -> GLfloat +> t i = 0.5 + 0.5*cos( fromIntegral i / 10 ) +> in +> Color3 (t n) (t (n+5)) (t (n+10)) + +> mandel x y = +> let r = 2.0 * x / width +> i = 2.0 * y / height +> in +> f (complex r i) 0 64 + +> f :: Complex -> Complex -> Int -> Int +> f c z 0 = 0 +> f c z n = if (magnitude z > 2 ) +> then n +> else f c ((z*z)+c) (n-1) + +
    + diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/03_Mandelbulb/Mandelbulb.lhs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/03_Mandelbulb/Mandelbulb.lhs new file mode 100644 index 0000000..fc20dc0 --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/03_Mandelbulb/Mandelbulb.lhs @@ -0,0 +1,378 @@ + ## 3D Mandelbrot? + +Now we will we extend to a third dimension. +But, there is no 3D equivalent to complex. +In fact, the only extension known are quaternions (in 4D). +As I know almost nothing about quaternions, I will use some extended complex, +instead of using a 3D projection of quaternions. +I am pretty sure this construction is not useful for numbers. +But it will be enough for us to create something that look nice. + +This section is quite long, but don't be afraid, +most of the code is some OpenGL boilerplate. +If you just want to skim this section, +here is a high level representation: + + > - OpenGL Boilerplate + > + > - set some IORef (understand variables) for states + > - Drawing: + > + > - set doubleBuffer, handle depth, window size... + > - Use state to apply some transformations + > + > - Keyboard: hitting some key change the state of IORef + > + > - Generate 3D Object + > + > ~~~ + > allPoints :: [ColoredPoint] + > allPoints = + > for all (x,y), -width Let z be the minimal depth such that + > mandel x y z > 0 + > add the points + > (x, y, z,color) + > (x,-y, z,color) + > (x, y,-z,color) + > (x,-y,-z,color) + > + neighbors to make triangles + > ~~~ + + + +
    + +> import Graphics.Rendering.OpenGL +> import Graphics.UI.GLUT +> import Data.IORef +> type ColoredPoint = (GLfloat,GLfloat,GLfloat,Color3 GLfloat) + +
    + +We declare a new type `ExtComplex` (for extended complex). +An extension of complex numbers with a third component: + +> data ExtComplex = C (GLfloat,GLfloat,GLfloat) +> deriving (Show,Eq) +> instance Num ExtComplex where +> -- The shape of the 3D mandelbrot +> -- will depend on this formula +> C (x,y,z) * C (x',y',z') = C (x*x' - y*y' - z*z', +> x*y' + y*x' + z*z', +> x*z' + z*x' ) +> -- The rest is straightforward +> fromInteger n = C (fromIntegral n, 0, 0) +> C (x,y,z) + C (x',y',z') = C (x+x', y+y', z+z') +> abs (C (x,y,z)) = C (sqrt (x*x + y*y + z*z), 0, 0) +> signum (C (x,y,z)) = C (signum x, signum y, signum z) + +The most important part is the new multiplication instance. +Modifying this formula will change radically the shape of the result. +Here is the formula written in a more mathematical notation. +I called the third component of these extended complex _strange_. + +$$ \mathrm{real} ((x,y,z) * (x',y',z')) = xx' - yy' - zz' $$ + +$$ \mathrm{im} ((x,y,z) * (x',y',z')) = xy' - yx' + zz' $$ + +$$ \mathrm{strange} ((x,y,z) * (x',y',z')) = xz' + zx' $$ + +Note how if `z=z'=0` then the multiplication is the same to the complex one. + +
    + +> extcomplex :: GLfloat -> GLfloat -> GLfloat -> ExtComplex +> extcomplex x y z = C (x,y,z) +> +> real :: ExtComplex -> GLfloat +> real (C (x,y,z)) = x +> +> im :: ExtComplex -> GLfloat +> im (C (x,y,z)) = y +> +> strange :: ExtComplex -> GLfloat +> strange (C (x,y,z)) = z +> +> magnitude :: ExtComplex -> GLfloat +> magnitude = real.abs + +
    + + ### From 2D to 3D + +As we will use some 3D, we add some new directive in the boilerplate. +But mainly, we simply state that will use some depth buffer. +And also we will listen the keyboard. + +> main :: IO () +> main = do +> -- GLUT need to be initialized +> (progname,_) <- getArgsAndInitialize +> -- We will use the double buffered mode (GL constraint) +> -- We also Add the DepthBuffer (for 3D) +> initialDisplayMode $= +> [WithDepthBuffer,DoubleBuffered,RGBMode] +> -- We create a window with some title +> createWindow "3D HOpengGL Mandelbrot" +> -- We add some directives +> depthFunc $= Just Less +> windowSize $= Size 500 500 +> -- Some state variables (I know it feels BAD) +> angle <- newIORef ((35,0)::(GLfloat,GLfloat)) +> zoom <- newIORef (2::GLfloat) +> campos <- newIORef ((0.7,0)::(GLfloat,GLfloat)) +> -- Function to call each frame +> idleCallback $= Just idle +> -- Function to call when keyboard or mouse is used +> keyboardMouseCallback $= +> Just (keyboardMouse angle zoom campos) +> -- Each time we will need to update the display +> -- we will call the function 'display' +> -- But this time, we add some parameters +> displayCallback $= display angle zoom campos +> -- We enter the main loop +> mainLoop + +The `idle` is here to change the states. +There should never be any modification done in the `display` function. + +> idle = postRedisplay Nothing + +We introduce some helper function to manipulate +standard `IORef`. +Mainly `modVar x f` is equivalent to the imperative `x:=f(x)`, +`modFst (x,y) (+1)` is equivalent to `(x,y) := (x+1,y)` +and `modSnd (x,y) (+1)` is equivalent to `(x,y) := (x,y+1)` + +> modVar v f = do +> v' <- get v +> v $= (f v') +> mapFst f (x,y) = (f x, y) +> mapSnd f (x,y) = ( x,f y) + +And we use them to code the function handling keyboard. +We will use the keys `hjkl` to rotate, +`oi` to zoom and `sedf` to move. +Also, hitting space will reset the view. +Remember that `angle` and `campos` are pairs and `zoom` is a scalar. +Also note `(+0.5)` is the function `\x->x+0.5` +and `(-0.5)` is the number `-0.5` (yes I share your pain). + +> keyboardMouse angle zoom campos key state modifiers position = +> -- We won't use modifiers nor position +> kact angle zoom campos key state +> where +> -- reset view when hitting space +> kact a z p (Char ' ') Down = do +> a $= (0,0) -- angle +> z $= 1 -- zoom +> p $= (0,0) -- camera position +> -- use of hjkl to rotate +> kact a _ _ (Char 'h') Down = modVar a (mapFst (+0.5)) +> kact a _ _ (Char 'l') Down = modVar a (mapFst (+(-0.5))) +> kact a _ _ (Char 'j') Down = modVar a (mapSnd (+0.5)) +> kact a _ _ (Char 'k') Down = modVar a (mapSnd (+(-0.5))) +> -- use o and i to zoom +> kact _ z _ (Char 'o') Down = modVar z (*1.1) +> kact _ z _ (Char 'i') Down = modVar z (*0.9) +> -- use sdfe to move the camera +> kact _ _ p (Char 's') Down = modVar p (mapFst (+0.1)) +> kact _ _ p (Char 'f') Down = modVar p (mapFst (+(-0.1))) +> kact _ _ p (Char 'd') Down = modVar p (mapSnd (+0.1)) +> kact _ _ p (Char 'e') Down = modVar p (mapSnd (+(-0.1))) +> -- any other keys does nothing +> kact _ _ _ _ _ = return () + +Note `display` takes some parameters this time. +This function if full of boilerplate: + +> display angle zoom position = do +> -- set the background color (dark solarized theme) +> clearColor $= Color4 0 0.1686 0.2117 1 +> clear [ColorBuffer,DepthBuffer] +> -- Transformation to change the view +> loadIdentity -- reset any transformation +> -- tranlate +> (x,y) <- get position +> translate $ Vector3 x y 0 +> -- zoom +> z <- get zoom +> scale z z z +> -- rotate +> (xangle,yangle) <- get angle +> rotate xangle $ Vector3 1.0 0.0 (0.0::GLfloat) +> rotate yangle $ Vector3 0.0 1.0 (0.0::GLfloat) +> +> -- Now that all transformation were made +> -- We create the object(s) +> preservingMatrix drawMandelbrot +> +> swapBuffers -- refresh screen + +Not much to say about this function. +Mainly there are two parts: apply some transformations, draw the object. + + ### The 3D Mandelbrot + +We have finished with the OpenGL section, let's talk about how we +generate the 3D points and colors. +First, we will set the number of details to 200 pixels in the three dimensions. + +> nbDetails = 200 :: GLfloat +> width = nbDetails +> height = nbDetails +> deep = nbDetails + +This time, instead of just drawing some line or some group of points, +we will show triangles. +The function `allPoints` will provide a multiple of three points. +Each three successive point representing the coordinate of each vertex of a triangle. + + +> drawMandelbrot = do +> -- We will print Points (not triangles for example) +> renderPrimitive Triangles $ do +> mapM_ drawColoredPoint allPoints +> where +> drawColoredPoint (x,y,z,c) = do +> color c +> vertex $ Vertex3 x y z + +In fact, we will provide six ordered points. +These points will be used to draw two triangles. + +blogimage("triangles.png","Explain triangles") + +The next function is a bit long. +Here is an approximative English version: + +~~~ +forall x from -width to width + forall y from -height to height + forall the neighbors of (x,y) + let z be the smalled depth such that (mandel x y z)>0 + let c be the color given by mandel x y z + add the point corresponding to (x,y,z,c) +~~~ + +Also, I added a test to hide points too far from the border. +In fact, this function show points close to the surface of the modified mandelbrot set. But not the mandelbrot set itself. + + +depthPoints :: [ColoredPoint] +depthPoints = do + x <- [-width..width] + y <- [-height..height] + let + depthOf x' y' = maxZeroIndex (mandel x' y') 0 deep logdeep + logdeep = floor ((log deep) / log 2) + z1 = depthOf x y + z2 = depthOf (x+1) y + z3 = depthOf (x+1) (y+1) + z4 = depthOf x (y+1) + c1 = mandel x y (z1+1) + c2 = mandel (x+1) y (z2+1) + c3 = mandel (x+1) (y+1) (z3+1) + c4 = mandel x (y+1) (z4+1) + p1 = ( x /width, y /height, z1/deep, colorFromValue c1) + p2 = ((x+1)/width, y /height, z2/deep, colorFromValue c2) + p3 = ((x+1)/width,(y+1)/height, z3/deep, colorFromValue c3) + p4 = ( x /width,(y+1)/height, z4/deep, colorFromValue c4) + if (and $ map (>=57) [c1,c2,c3,c4]) + then [] + else [p1,p2,p3,p1,p3,p4] + + +If you look at the function above, you see a lot of common patterns. +Haskell is very efficient to make this better. +Here is a harder to read but shorter and more generic rewritten function: + +> depthPoints :: [ColoredPoint] +> depthPoints = do +> x <- [-width..width] +> y <- [-height..height] +> let +> neighbors = [(x,y),(x+1,y),(x+1,y+1),(x,y+1)] +> depthOf (u,v) = maxZeroIndex (mandel u v) 0 deep logdeep +> logdeep = floor ((log deep) / log 2) +> -- zs are 3D points with found depth +> zs = map (\(u,v) -> (u,v,depthOf (u,v))) neighbors +> -- ts are 3D pixels + mandel value +> ts = map (\(u,v,w) -> (u,v,w,mandel u v (w+1))) zs +> -- ps are 3D opengl points + color value +> ps = map (\(u,v,w,c') -> +> (u/width,v/height,w/deep,colorFromValue c')) ts +> -- If the point diverged too fast, don't display it +> if (and $ map (\(_,_,_,c) -> c>=57) ts) +> then [] +> -- Draw two triangles +> else [ps!!0,ps!!1,ps!!2,ps!!0,ps!!2,ps!!3] + +If you prefer the first version, then just imagine how hard it will be to change the enumeration of the point from (x,y) to (x,z) for example. + +Also, we didn't searched for negative values. +This modified Mandelbrot is no more symmetric relatively to the plan `y=0`. +But it is symmetric relatively to the plan `z=0`. +Then I mirror these values. + +> allPoints :: [ColoredPoint] +> allPoints = planPoints ++ map inverseDepth planPoints +> where +> planPoints = depthPoints +> inverseDepth (x,y,z,c) = (x,y,-z+1/deep,c) + +The rest of the program is very close to the preceding one. + +
    + +> -- given f min max nbtest, +> -- considering +> -- - f is an increasing function +> -- - f(min)=0 +> -- - f(max)≠0 +> -- then maxZeroIndex f min max nbtest returns x such that +> -- f(x - ε)=0 and f(x + ε)≠0 +> -- where ε=(max-min)/2^(nbtest+1) +> maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => +> (a -> b) -> a -> a -> Int -> a +> maxZeroIndex func minval maxval 0 = (minval+maxval)/2 +> maxZeroIndex func minval maxval n = +> if (func medpoint) /= 0 +> then maxZeroIndex func minval medpoint (n-1) +> else maxZeroIndex func medpoint maxval (n-1) +> where medpoint = (minval+maxval)/2 + +I made the color slightly brighter + +> colorFromValue n = +> let +> t :: Int -> GLfloat +> t i = 0.7 + 0.3*cos( fromIntegral i / 10 ) +> in +> Color3 (t n) (t (n+5)) (t (n+10)) + +We only changed from `Complex` to `ExtComplex` of the main `f` function. + +> f :: ExtComplex -> ExtComplex -> Int -> Int +> f c z 0 = 0 +> f c z n = if (magnitude z > 2 ) +> then n +> else f c ((z*z)+c) (n-1) + +
    + +We simply add a new dimension to the `mandel` function +and change the type signature of `f` from `Complex` to `ExtComplex`. + +> mandel x y z = +> let r = 2.0 * x / width +> i = 2.0 * y / height +> s = 2.0 * z / deep +> in +> f (extcomplex r i s) 0 64 + + +Here is the result: + +blogimage("mandelbrot_3D.png","A 3D mandelbrot like") diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/ExtComplex.hs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/ExtComplex.hs new file mode 100644 index 0000000..caba8d0 --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/ExtComplex.hs @@ -0,0 +1,37 @@ +module ExtComplex where + +import Graphics.Rendering.OpenGL + +-- This time I use unpacked strict data type +-- Far faster when compiled. +data ExtComplex = C {-# UNPACK #-} !GLfloat + {-# UNPACK #-} !GLfloat + {-# UNPACK #-} !GLfloat + deriving (Show,Eq) + +instance Num ExtComplex where + -- The shape of the 3D mandelbrot + -- will depend on this formula + (C x y z) * (C x' y' z') = C (x*x' - y*y' - z*z') + (x*y' + y*x' + z*z') + (x*z' + z*x' ) + -- The rest is straightforward + fromInteger n = C (fromIntegral n) 0 0 + (C x y z) + (C x' y' z') = C (x+x') (y+y') (z+z') + abs (C x y z) = C (sqrt (x*x + y*y + z*z)) 0 0 + signum (C x y z) = C (signum x) (signum y) (signum z) + +extcomplex :: GLfloat -> GLfloat -> GLfloat -> ExtComplex +extcomplex x y z = C x y z + +real :: ExtComplex -> GLfloat +real (C x _ _) = x + +im :: ExtComplex -> GLfloat +im (C _ y _) = y + +strange :: ExtComplex -> GLfloat +strange (C _ _ z) = z + +magnitude :: ExtComplex -> GLfloat +magnitude = real.abs diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandel.hs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandel.hs new file mode 100644 index 0000000..9500e0b --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandel.hs @@ -0,0 +1,13 @@ +-- The Mandelbrot function +module Mandel (mandel) where + +import ExtComplex + +mandel r i s nbIterations = + f (extcomplex r i s) 0 nbIterations + where + f :: ExtComplex -> ExtComplex -> Int -> Int + f c z 0 = 0 + f c z n = if (magnitude z > 2 ) + then n + else f c ((z*z)+c) (n-1) diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandelbulb.lhs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandelbulb.lhs new file mode 100644 index 0000000..d9fb813 --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandelbulb.lhs @@ -0,0 +1,90 @@ + ## Naïve code cleaning + +The first approach to clean the code is to separate the GLUT/OpenGL +part from the computation of the shape. +Here is the cleaned version of the preceding section. +Most boilerplate was put in external files. + +- [`YBoiler.hs`](code/04_Mandelbulb/YBoiler.hs), the 3D rendering +- [`Mandel`](code/04_Mandelbulb/Mandel.hs), the mandel function +- [`ExtComplex`](code/04_Mandelbulb/ExtComplex.hs), the extended complexes + +> import YBoiler -- Most the OpenGL Boilerplate +> import Mandel -- The 3D Mandelbrot maths + +The `yMainLoop` takes two arguments: +the title of the window +and a function from time to triangles + +> main :: IO () +> main = yMainLoop "3D Mandelbrot" (\_ -> allPoints) + +We set some global constant (this is generally bad). + +> nbDetails = 200 :: GLfloat +> width = nbDetails +> height = nbDetails +> deep = nbDetails + +We then generate colored points from our function. +This is similar to the preceding section. + +> allPoints :: [ColoredPoint] +> allPoints = planPoints ++ map inverseDepth planPoints +> where +> planPoints = depthPoints ++ map inverseHeight depthPoints +> inverseHeight (x,y,z,c) = (x,-y,z,c) +> inverseDepth (x,y,z,c) = (x,y,-z+1/deep,c) + +> depthPoints :: [ColoredPoint] +> depthPoints = do +> x <- [-width..width] +> y <- [0..height] +> let +> neighbors = [(x,y),(x+1,y),(x+1,y+1),(x,y+1)] +> depthOf (u,v) = maxZeroIndex (ymandel u v) 0 deep 7 +> -- zs are 3D points with found depth +> zs = map (\(u,v) -> (u,v,depthOf (u,v))) neighbors +> -- ts are 3D pixels + mandel value +> ts = map (\(u,v,w) -> (u,v,w,ymandel u v (w+1))) zs +> -- ps are 3D opengl points + color value +> ps = map (\(u,v,w,c') -> +> (u/width,v/height,w/deep,colorFromValue c')) ts +> -- If the point diverged too fast, don't display it +> if (and $ map (\(_,_,_,c) -> c>=57) ts) +> then [] +> -- Draw two triangles +> else [ps!!0,ps!!1,ps!!2,ps!!0,ps!!2,ps!!3] +> +> +> -- given f min max nbtest, +> -- considering +> -- - f is an increasing function +> -- - f(min)=0 +> -- - f(max)≠0 +> -- then maxZeroIndex f min max nbtest returns x such that +> -- f(x - ε)=0 and f(x + ε)≠0 +> -- where ε=(max-min)/2^(nbtest+1) +> maxZeroIndex func minval maxval 0 = (minval+maxval)/2 +> maxZeroIndex func minval maxval n = +> if (func medpoint) /= 0 +> then maxZeroIndex func minval medpoint (n-1) +> else maxZeroIndex func medpoint maxval (n-1) +> where medpoint = (minval+maxval)/2 +> +> colorFromValue n = +> let +> t :: Int -> GLfloat +> t i = 0.7 + 0.3*cos( fromIntegral i / 10 ) +> in +> ((t n),(t (n+5)),(t (n+10))) +> +> ymandel x y z = mandel (2*x/width) (2*y/height) (2*z/deep) 64 + +This code is cleaner but many things doesn't feel right. +First, all the user interaction code is outside our main file. +I feel it is okay to hide the detail for the rendering. +But I would have preferred to control the user actions. + +On the other hand, we continue to handle a lot rendering details. +For example, we provide ordered vertices. diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/YBoiler.hs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/YBoiler.hs new file mode 100644 index 0000000..867f8b7 --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/YBoiler.hs @@ -0,0 +1,120 @@ +-- An OpenGL boilerplate +module YBoiler (GLfloat,yMainLoop,ColoredPoint,Color3) where + +import Graphics.Rendering.OpenGL +import Graphics.UI.GLUT +import Data.IORef + + +type ColorRGB = (GLfloat,GLfloat,GLfloat) +type YAngle = (GLfloat,GLfloat,GLfloat) +type ColoredPoint = (GLfloat,GLfloat,GLfloat,ColorRGB) + +yMainLoop :: String -> (Int -> [ColoredPoint]) -> IO () +yMainLoop windowTitle triangles = do + -- GLUT need to be initialized + (progname,_) <- getArgsAndInitialize + -- We will use the double buffered mode (GL constraint) + -- We also Add the DepthBuffer (for 3D) + initialDisplayMode $= + [WithDepthBuffer,DoubleBuffered,RGBMode] + -- We create a window with some title + createWindow windowTitle + -- We add some directives + depthFunc $= Just Less + -- matrixMode $= Projection + windowSize $= Size 500 500 + -- Some state variables (I know it feels BAD) + angle <- newIORef ((35,0,0)::YAngle) + zoom <- newIORef (2::GLfloat) + campos <- newIORef ((0.7,0)::(GLfloat,GLfloat)) + -- Action to call when waiting + idleCallback $= Just idle + -- We will use the keyboard + keyboardMouseCallback $= + Just (keyboardMouse angle zoom campos) + -- Each time we will need to update the display + -- we will call the function 'display' + -- But this time, we add some parameters + displayCallback $= display angle zoom campos triangles + -- We enter the main loop + mainLoop + +idle = postRedisplay Nothing + +-- modify IORef cleanly +modVar :: IORef a -> (a -> a) -> IO () +modVar v f = do + v' <- get v + v $= (f v') +-- modify IORef (a,b) using f:a->a +mapFst f (x,y) = (f x, y) +-- modify IORef (a,b) using f:b->b +mapSnd f (x,y) = ( x,f y) +mapFst3 f (x,y,z) = (f x, y, z) +mapSnd3 f (x,y,z) = (x, f y, z) +mapThi3 f (x,y,z) = (x, y, f z) + +-- Get User Input +keyboardMouse angle zoom pos key state modifiers position = + kact angle zoom pos key state + where + -- reset view when hitting space + kact a z p (Char ' ') Down = do + a $= (0,0,0) + z $= 1 + p $= (0,0) + -- use of hjkl to rotate + kact a _ _ (Char 'j') Down = modVar a (mapFst3 (+0.5)) + kact a _ _ (Char 'l') Down = modVar a (mapFst3 (+(-0.5))) + kact a _ _ (Char 'i') Down = modVar a (mapSnd3 (+0.5)) + kact a _ _ (Char 'k') Down = modVar a (mapSnd3 (+(-0.5))) + kact a _ _ (Char 'o') Down = modVar a (mapThi3 (+0.5)) + kact a _ _ (Char 'u') Down = modVar a (mapThi3 (+(-0.5))) + -- use o and i to zoom + kact _ s _ (Char '+') Down = modVar s (*1.1) + kact _ s _ (Char '-') Down = modVar s (*0.9) + -- use sdfe to move the camera + kact _ _ p (Char 's') Down = modVar p (mapFst (+0.1)) + kact _ _ p (Char 'f') Down = modVar p (mapFst (+(-0.1))) + kact _ _ p (Char 'd') Down = modVar p (mapSnd (+0.1)) + kact _ _ p (Char 'e') Down = modVar p (mapSnd (+(-0.1))) + -- any other keys does nothing + kact _ _ _ _ _ = return () + +-- The function that will display datas +display angle zoom position triangles = do + -- set the background color (dark solarized theme) + clearColor $= Color4 0 0.1686 0.2117 1 + clear [ColorBuffer,DepthBuffer] + -- Transformation to change the view + loadIdentity -- reset any transformation + -- tranlate + (x,y) <- get position + translate $ Vector3 x y 0 + -- zoom + z <- get zoom + scale z z z + -- rotate + (xangle,yangle,zangle) <- get angle + rotate xangle $ Vector3 1.0 0.0 (0.0::GLfloat) + rotate yangle $ Vector3 0.0 1.0 (0.0::GLfloat) + rotate zangle $ Vector3 0.0 0.0 (1.0::GLfloat) + -- Now that all transformation were made + -- We create the object(s) + t <- get elapsedTime + preservingMatrix $ drawObject (triangles t) + swapBuffers -- refresh screen + +red (r,_,_) = r +green (_,g,_) = g +blue (_,_,b) = b + +drawObject triangles = do + -- We will print Points (not triangles for example) + renderPrimitive Triangles $ do + mapM_ drawColoredPoint triangles + where + drawColoredPoint (x,y,z,c) = do + color $ Color3 (red c) (green c) (blue c) + vertex $ Vertex3 x y z diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/ExtComplex.hs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/ExtComplex.hs new file mode 100644 index 0000000..caba8d0 --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/ExtComplex.hs @@ -0,0 +1,37 @@ +module ExtComplex where + +import Graphics.Rendering.OpenGL + +-- This time I use unpacked strict data type +-- Far faster when compiled. +data ExtComplex = C {-# UNPACK #-} !GLfloat + {-# UNPACK #-} !GLfloat + {-# UNPACK #-} !GLfloat + deriving (Show,Eq) + +instance Num ExtComplex where + -- The shape of the 3D mandelbrot + -- will depend on this formula + (C x y z) * (C x' y' z') = C (x*x' - y*y' - z*z') + (x*y' + y*x' + z*z') + (x*z' + z*x' ) + -- The rest is straightforward + fromInteger n = C (fromIntegral n) 0 0 + (C x y z) + (C x' y' z') = C (x+x') (y+y') (z+z') + abs (C x y z) = C (sqrt (x*x + y*y + z*z)) 0 0 + signum (C x y z) = C (signum x) (signum y) (signum z) + +extcomplex :: GLfloat -> GLfloat -> GLfloat -> ExtComplex +extcomplex x y z = C x y z + +real :: ExtComplex -> GLfloat +real (C x _ _) = x + +im :: ExtComplex -> GLfloat +im (C _ y _) = y + +strange :: ExtComplex -> GLfloat +strange (C _ _ z) = z + +magnitude :: ExtComplex -> GLfloat +magnitude = real.abs diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandel.hs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandel.hs new file mode 100644 index 0000000..7c1ef34 --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandel.hs @@ -0,0 +1,14 @@ +-- The Mandelbrot function +module Mandel (mandel) where + +import ExtComplex + +mandel :: Float -> Float -> Float -> Int -> Int +mandel r i s nbIterations = + f (extcomplex r i s) 0 nbIterations + where + f :: ExtComplex -> ExtComplex -> Int -> Int + f _ _ 0 = 0 + f c z n = if (magnitude z > 2 ) + then n + else f c ((z*z)+c) (n-1) diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandelbulb.lhs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandelbulb.lhs new file mode 100644 index 0000000..f58abff --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandelbulb.lhs @@ -0,0 +1,250 @@ + ## Functional organization? + +Some points: + +1. OpenGL and GLUT is done in C. + In particular the `mainLoop` function is a direct link to the C library (FFI). + This function is clearly far from the functional paradigm. + Could we make this better? + We will have two choices: + + - create our own `mainLoop` function to make it more functional. + - deal with the imperative nature of the GLUT `mainLoop` function. + + As one of the goal of this article is to understand how to deal with existing libraries and particularly the one coming from imperative languages, we will continue to use the `mainLoop` function. +2. Our main problem come from user interaction. + If you ask "the Internet", + about how to deal with user interaction with a functional paradigm, + the main answer is to use _functional reactive programming_ (FRP). + I won't use FRP in this article. + Instead, I'll use a simpler while less effective way to deal with user interaction. + But The method I'll use will be as pure and functional as possible. + +Here is how I imagine things should go. +First, what the main loop should look like if we could make our own: + + +functionalMainLoop = + Read user inputs and provide a list of actions + Apply all actions to the World + Display one frame + repetere aeternum + + +Clearly, ideally we should provide only three parameters to this main loop function: + +- an initial World state +- a mapping between the user interactions and functions which modify the world +- a function taking two parameters: time and world state and render a new world without user interaction. + +Here is a real working code, I've hidden most display functions. +The YGL, is a kind of framework to display 3D functions. +But it can easily be extended to many kind of representation. + +> import YGL -- Most the OpenGL Boilerplate +> import Mandel -- The 3D Mandelbrot maths + +We first set the mapping between user input and actions. +The type of each couple should be of the form +`(user input, f)` where (in a first time) `f:World -> World`. +It means, the user input will transform the world state. + +> -- Centralize all user input interaction +> inputActionMap :: InputMap World +> inputActionMap = inputMapFromList [ +> (Press 'k' , rotate xdir 5) +> ,(Press 'i' , rotate xdir (-5)) +> ,(Press 'j' , rotate ydir 5) +> ,(Press 'l' , rotate ydir (-5)) +> ,(Press 'o' , rotate zdir 5) +> ,(Press 'u' , rotate zdir (-5)) +> ,(Press 'f' , translate xdir 0.1) +> ,(Press 's' , translate xdir (-0.1)) +> ,(Press 'e' , translate ydir 0.1) +> ,(Press 'd' , translate ydir (-0.1)) +> ,(Press 'z' , translate zdir 0.1) +> ,(Press 'r' , translate zdir (-0.1)) +> ,(Press '+' , zoom 1.1) +> ,(Press '-' , zoom (1/1.1)) +> ,(Press 'h' , resize 1.2) +> ,(Press 'g' , resize (1/1.2)) +> ] + +And of course a type design the World State. +The important part is that it is our World State type. +We could have used any kind of data type. + +> -- I prefer to set my own name for these types +> data World = World { +> angle :: Point3D +> , scale :: Scalar +> , position :: Point3D +> , shape :: Scalar -> Function3D +> , box :: Box3D +> , told :: Time -- last frame time +> } + +The important part to glue our own type to the framework +is to make our type an instance of the type class `DisplayableWorld`. +We simply have to provide the definition of some functions. + +> instance DisplayableWorld World where +> winTitle _ = "The YGL Mandelbulb" +> camera w = Camera { +> camPos = position w, +> camDir = angle w, +> camZoom = scale w } +> -- objects for world w +> -- is the list of one unique element +> -- The element is an YObject +> -- more precisely the XYFunc Function3D Box3D +> -- where the Function3D is the type +> -- Point -> Point -> Maybe (Point,Color) +> -- and its value here is ((shape w) res) +> -- and the Box3D value is defbox +> objects w = [XYFunc ((shape w) res) defbox] +> where +> res = resolution $ box w +> defbox = box w + +The `camera` function will retrieve an object of type `Camera` which contains +most necessary information to set our camera. +The `objects` function will returns a list of objects. +Their type is `YObject`. Note the generation of triangles is no more in this file. +Until here we only used declarative pattern. + +We also need to set all our transformation functions. +These function are used to update the world state. + +> xdir :: Point3D +> xdir = makePoint3D (1,0,0) +> ydir :: Point3D +> ydir = makePoint3D (0,1,0) +> zdir :: Point3D +> zdir = makePoint3D (0,0,1) + +Note `(-*<)` is the scalar product (`α -*< (x,y,z) = (αx,αy,αz)`). +Also note we could add two Point3D. + +> rotate :: Point3D -> Scalar -> World -> World +> rotate dir angleValue world = +> world { +> angle = (angle world) + (angleValue -*< dir) } +> +> translate :: Point3D -> Scalar -> World -> World +> translate dir len world = +> world { +> position = (position world) + (len -*< dir) } +> +> zoom :: Scalar -> World -> World +> zoom z world = world { +> scale = z * scale world } +> +> resize :: Scalar -> World -> World +> resize r world = world { +> box = (box world) { +> resolution = sqrt ((resolution (box world))**2 * r) }} + +The resize is used to generate the 3D function. +As I wanted the time spent to generate a more detailed view +to grow linearly I use this not so straightforward formula. + +The `yMainLoop` takes three arguments. + +- A map between user Input and world transformation +- A timed world transformation +- An initial world state + +> main :: IO () +> main = yMainLoop inputActionMap idleAction initialWorld + +Here is our initial world state. + +> -- We initialize the world state +> -- then angle, position and zoom of the camera +> -- And the shape function +> initialWorld :: World +> initialWorld = World { +> angle = makePoint3D (-30,-30,0) +> , position = makePoint3D (0,0,0) +> , scale = 0.8 +> , shape = shapeFunc +> , box = Box3D { minPoint = makePoint3D (-2,-2,-2) +> , maxPoint = makePoint3D (2,2,2) +> , resolution = 0.16 } +> , told = 0 +> } + +We will define `shapeFunc` later. +Here is the function which transform the world even without user action. +Mainly it makes some rotation. + +> idleAction :: Time -> World -> World +> idleAction tnew world = world { +> angle = (angle world) + (delta -*< zdir) +> , told = tnew +> } +> where +> anglePerSec = 5.0 +> delta = anglePerSec * elapsed / 1000.0 +> elapsed = fromIntegral (tnew - (told world)) + +Now the function which will generate points in 3D. +The first parameter (`res`) is the resolution of the vertex generation. +More precisely, `res` is distance between two points on one direction. +We need it to "close" our shape. + +The type `Function3D` is `Point -> Point -> Maybe Point`. +Because we consider partial functions +(for some `(x,y)` our function can be undefined). + +> shapeFunc :: Scalar -> Function3D +> shapeFunc res x y = +> let +> z = maxZeroIndex (ymandel x y) 0 1 20 +> in +> if and [ maxZeroIndex (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 | +> val <- [res], xeps <- [-val,val], yeps<-[-val,val]] +> then Nothing +> else Just (z,colorFromValue ((ymandel x y z) * 64)) + +With the color function. + +> colorFromValue :: Point -> Color +> colorFromValue n = +> let +> t :: Point -> Scalar +> t i = 0.7 + 0.3*cos( i / 10 ) +> in +> makeColor (t n) (t (n+5)) (t (n+10)) + +The rest is similar to the preceding sections. + +> -- given f min max nbtest, +> -- considering +> -- - f is an increasing function +> -- - f(min)=0 +> -- - f(max)≠0 +> -- then maxZeroIndex f min max nbtest returns x such that +> -- f(x - ε)=0 and f(x + ε)≠0 +> -- where ε=(max-min)/2^(nbtest+1) +> maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => +> (a -> b) -> a -> a -> Int -> a +> maxZeroIndex _ minval maxval 0 = (minval+maxval)/2 +> maxZeroIndex func minval maxval n = +> if (func medpoint) /= 0 +> then maxZeroIndex func minval medpoint (n-1) +> else maxZeroIndex func medpoint maxval (n-1) +> where medpoint = (minval+maxval)/2 +> +> ymandel :: Point -> Point -> Point -> Point +> ymandel x y z = fromIntegral (mandel x y z 64) / 64 + +I won't explain how the magic occurs here. +If you are interested, just read the file [`YGL.hs`](code/05_Mandelbulb/YGL.hs). +It is commented a lot. + +- [`YGL.hs`](code/05_Mandelbulb/YGL.hs), the 3D rendering framework +- [`Mandel`](code/05_Mandelbulb/Mandel.hs), the mandel function +- [`ExtComplex`](code/05_Mandelbulb/ExtComplex.hs), the extended complexes + diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/YGL.hs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/YGL.hs new file mode 100644 index 0000000..2f6e166 --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/YGL.hs @@ -0,0 +1,383 @@ +{- +The module YGL will contains most boilerplate +And display details. + +To make things even nicer, we should separate +this file in many different parts. +Typically separate the display function. + +-} +module YGL ( + -- Here is declared our interface with external files + -- that will include our YGL module + + -- Declarations related to data types + Point -- the 1 dimension point type + , Time -- the type for the time + , Scalar -- the type for scalar values + , Color -- the type for color (3 scalars) + , Point3D (..) -- A 3D point type (3 Points) + , makePoint3D -- helper (x,y,z) -> Point3D + , (-*<) -- scalar product on Point3D a -*< (x,y,z) = (ax,ay,az) + , Function3D -- Point -> Point -> Maybe (Point,Color) + + -- Your world state must be an instance + -- of the DisplayableWorld type class + , DisplayableWorld (..) + -- Datas related to DisplayableWorld + , Camera (..) + , YObject (..) -- 3D Objects to display + , Box3D (..) -- Some bounded 3D box + , makeBox -- helper to make a box + , hexColor -- Color from hexadecimal string + , makeColor -- make color from RGB values + -- Interface related to user input + , InputMap + , UserInput (Press,Ctrl,Alt,CtrlAlt) + , inputMapFromList + + -- The main loop function to call + , yMainLoop +) where + +-- A bunch of imports +import Numeric (readHex) -- to read hexadecimal values + +-- Import of OpenGL and GLUT +-- but, I use my own Color type, therefore I hide the definition +-- of Color inside GLUT and OpenGL packages +import Graphics.Rendering.OpenGL hiding (Color) +import Graphics.UI.GLUT hiding (Color) +import Data.IORef + +-- I use Map to deal with user interaction +import qualified Data.Map as Map + +-- Some standard stuff +import Control.Monad (when) +import Data.Maybe (isNothing) + +{-- Things start to be complex here. +- Just take the time to follow me. +--} + +-- | A 1D point +type Point = GLfloat +-- | A Scalar value +type Scalar = GLfloat +-- | The time type (currently its Int) +type Time = Int +-- | A 3D Point mainly '(x,y,z)' +data Point3D = P (Point,Point,Point) deriving (Eq,Show,Read) +type Color = Color3 Scalar + +-- Get x (resp. y, z) coordinate of a 3D point +xpoint :: Point3D -> Point +xpoint (P (x,_,_)) = x +ypoint :: Point3D -> Point +ypoint (P (_,y,_)) = y +zpoint :: Point3D -> Point +zpoint (P (_,_,z)) = z + +-- Create a Point3D element from a triplet +makePoint3D :: (Point,Point,Point) -> Point3D +makePoint3D = P + +-- Make Point3D an instance of Num +instance Num Point3D where + (+) (P (ax,ay,az)) (P (bx,by,bz)) = P (ax+bx,ay+by,az+bz) + (-) (P (ax,ay,az)) (P (bx,by,bz)) = P (ax-bx,ay-by,az-bz) + (*) (P (ax,ay,az)) (P (bx,by,bz)) = P ( ay*bz - az*by + , az*bx - ax*bz + , ax*by - ay*bx ) + abs (P (x,y,z)) = P (abs x,abs y, abs z) + signum (P (x,y,z)) = P (signum x, signum y, signum z) + fromInteger i = P (fromInteger i, 0, 0) + +-- The scalar product +infixr 5 -*< +(-*<) :: Scalar -> Point3D -> Point3D +(-*<) s p = P (s*xpoint p, s*ypoint p, s*zpoint p) + +-- Used internally to convert point3D to different types +toGLVector3 :: Point3D -> Vector3 GLfloat +toGLVector3 (P(x,y,z)) = Vector3 x y z + +toGLVertex3 :: Point3D -> Vertex3 GLfloat +toGLVertex3 (P(x,y,z)) = Vertex3 x y z + +toGLNormal3 :: Point3D -> Normal3 GLfloat +toGLNormal3 (P(x,y,z)) = Normal3 x y z + +-- | The Box3D type represent a 3D bounding box +-- | Note if minPoint = (x,y,z) and maxPoint = (x',y',z') +-- | Then to have a non empty box you must have +-- | x (Point,Point,Point) -> Scalar -> Box3D +makeBox mini maxi res = Box3D { + minPoint = makePoint3D mini + , maxPoint = makePoint3D maxi + , resolution = res } + +-- | A Triangle3D is simply 3 points and a color +type Triangle3D = (Point3D,Point3D,Point3D,Color) + +-- | The type Atom is the atom for our display here we'll only use triangles. +-- | For a general purpose library we should add many other different atoms +-- | corresponding to Quads for example. +data Atom = ColoredTriangle Triangle3D + +-- | A Function3D is simply a function for each x,y associate a z and a color +-- | If undefined at point (x,y), it returns Nothing. +type Function3D = Point -> Point -> Maybe (Point,Color) + +-- | Our objects that will be displayed +-- | Wether a function3D delimited by a Box +-- | or a list of Atoms +data YObject = XYFunc Function3D Box3D + | Atoms [Atom] + +-- | The function atoms retrieve the list of atoms from an YObject +atoms :: YObject -> [Atom] +atoms (XYFunc f b) = getObject3DFromShapeFunction f b +atoms (Atoms atomList) = atomList + +-- | We decalre the input map type we need here +-- | It is our API +-- | I don't use Mouse but it can be easily added +type InputMap worldType = Map.Map UserInput (worldType -> worldType) +data UserInput = Press Char | Ctrl Char | Alt Char | CtrlAlt Char + deriving (Eq,Ord,Show,Read) + +-- | A displayable world is a type for which +-- | ther exists a function that provide sufficient informations +-- | to provide a camera, lights, objects and a window title. +class DisplayableWorld world where + camera :: world -> Camera + camera _ = defaultCamera + lights :: world -> [Light] + lights _ = [] + objects :: world -> [YObject] + objects _ = [] + winTitle :: world -> String + winTitle _ = "YGL" + +-- | the Camera type to know how to +-- | Transform the scene to see the right view. +data Camera = Camera { + camPos :: Point3D + , camDir :: Point3D + , camZoom :: Scalar } + +-- | A default initial camera +defaultCamera :: Camera +defaultCamera = Camera { + camPos = makePoint3D (0,0,0) + , camDir = makePoint3D (0,0,0) + , camZoom = 1 } + + +-- | Given a shape function and a delimited Box3D +-- | return a list of Atoms (here only colored triangles) to be displayed +getObject3DFromShapeFunction :: Function3D -> Box3D -> [Atom] +getObject3DFromShapeFunction shape box = do + x <- [xmin,xmin+res..xmax] + y <- [ymin,ymin+res..ymax] + let + neighbors = [(x,y),(x+res,y),(x+res,y+res),(x,y+res)] + -- zs are 3D points with found depth and color + -- zs :: [ (Point,Point,Point,Maybe (Point,Color) ] + zs = map (\(u,v) -> (u,v,shape u v)) neighbors + -- ps are 3D opengl points + color value + ps = zs + -- If the point diverged too fast, don't display it + if any (\(_,_,z) -> isNothing z) zs + then [] + -- Draw two triangles + -- 3 - 2 + -- | / | + -- 0 - 1 + -- The order is important + else + [ makeAtom (ps!!0) (ps!!2) (ps!!1) + , makeAtom (ps!!0) (ps!!3) (ps!!2) ] + where + makeAtom (p0x,p0y,Just (p0z,c0)) (p1x,p1y,Just (p1z,_)) (p2x,p2y,Just (p2z,_)) = + ColoredTriangle (makePoint3D (p0x,p0y,p0z) + ,makePoint3D (p1x,p1y,p1z) + ,makePoint3D (p2x,p2y,p2z) + ,c0) + makeAtom _ _ _ = error "Somethings wrong here" + + -- some naming to make it + -- easier to read + xmin = xpoint $ minPoint box + xmax = xpoint $ maxPoint box + ymin = ypoint $ minPoint box + ymax = ypoint $ maxPoint box + res = resolution box + +-- | Get the user input map from a list +inputMapFromList :: (DisplayableWorld world) => + [(UserInput,world -> world)] -> InputMap world +inputMapFromList = Map.fromList + +{-- +- We set our mainLoop function +- As you can see the code is _not_ pure +- and not even functionnal friendly! +- But when called, +- it will look like a pure functional function. +--} +yMainLoop :: (DisplayableWorld worldType) => + -- the mapping user input / world + InputMap worldType + -- function that modify the world + -> (Time -> worldType -> worldType) + -- the world state of type worldType + -> worldType + -- into IO () for obvious reason + -> IO () +yMainLoop inputActionMap + worldTranformer + world = do + -- The boilerplate + _ <- getArgsAndInitialize + initialDisplayMode $= + [WithDepthBuffer,DoubleBuffered,RGBMode] + _ <- createWindow $ winTitle world + depthFunc $= Just Less + windowSize $= Size 500 500 + -- The state variables for the world (I know it feels BAD) + worldRef <- newIORef world + -- Action to call when waiting + idleCallback $= Just (idle worldTranformer worldRef) + -- the keyboard will update the world + keyboardMouseCallback $= + Just (keyboardMouse inputActionMap worldRef) + -- We generate one frame using the callback + displayCallback $= display worldRef + -- let OpenGL resize normal vectors to unity + normalize $= Enabled + shadeModel $= Smooth + -- Lights (in a better version should be put elsewhere) + lighting $= Enabled + ambient (Light 0) $= Color4 0 0 0 1 + diffuse (Light 0) $= Color4 0.5 0.5 0.5 1 + specular (Light 0) $= Color4 1 1 1 1 + position (Light 0) $= Vertex4 1 1 0 1 + light (Light 0) $= Enabled + pointSmooth $= Enabled + + colorMaterial $= Just (Front,AmbientAndDiffuse) + materialDiffuse Front $= Color4 0.5 0.5 0.5 1 + materialAmbient Front $= Color4 0.5 0.5 0.5 1 + materialSpecular Front $= Color4 0.2 0.2 0.2 1 + materialEmission Front $= Color4 0.3 0.3 0.3 1 + materialShininess Front $= 90.0 + -- We enter the main loop + mainLoop + +-- When no user input entered do nothing +idle :: (Time -> worldType -> worldType) -> IORef worldType -> IO () +idle worldTranformer world = do + w <- get world + t <- get elapsedTime + world $= worldTranformer t w + postRedisplay Nothing + +-- | Get User Input +-- | both cleaner, terser and more expendable than the preceeding code +keyboardMouse :: InputMap a -> IORef a + -> Key -> KeyState -> Modifiers -> Position -> IO() +keyboardMouse input world key state _ _ = + when (state == Down) $ + let + charFromKey (Char c) = c + -- To complete if you want to finish it + charFromKey _ = '#' + + transformator = Map.lookup (Press (charFromKey key)) input + in + mayTransform transformator + where + mayTransform Nothing = return () + mayTransform (Just transform) = do + w <- get world + world $= transform w + + +-- | The function that will display datas +display :: (HasGetter g, DisplayableWorld world) => + g world -> IO () +display worldRef = do + -- BEWARE UGLINESS!!!! + -- SHOULD NEVER MODIFY worldRef HERE!!!! + -- + -- I SAID NEVER. + w <- get worldRef + -- NO REALLY, NEVER!!!! + -- If someone write a line starting by + -- w $= ... Shoot him immediately in the head + -- and refere to competent authorities + let cam = camera w + -- set the background color (dark solarized theme) + -- Could also be externalized to world state + clearColor $= Color4 0 0.1686 0.2117 1 + clear [ColorBuffer,DepthBuffer] + -- Transformation to change the view + loadIdentity -- reset any transformation + -- tranlate + translate $ toGLVector3 (camPos cam) + -- zoom + scale (camZoom cam) (camZoom cam) (camZoom cam) + -- rotate + rotate (xpoint (camDir cam)) $ Vector3 1.0 0.0 (0.0::GLfloat) + rotate (ypoint (camDir cam)) $ Vector3 0.0 1.0 (0.0::GLfloat) + rotate (zpoint (camDir cam)) $ Vector3 0.0 0.0 (1.0::GLfloat) + -- Now that all transformation were made + -- We create the object(s) + _ <- preservingMatrix $ mapM drawObject (objects w) + swapBuffers -- refresh screen + +-- Hexa style colors +scalarFromHex :: String -> Scalar +scalarFromHex = (/256) . fst . head . readHex + +-- | Color from CSS style color string +hexColor :: String -> Color +hexColor ('#':rd:ru:gd:gu:bd:bu:[]) = Color3 (scalarFromHex [rd,ru]) + (scalarFromHex [gd,gu]) + (scalarFromHex [bd,bu]) +hexColor ('#':r:g:b:[]) = hexColor ['#',r,r,g,g,b,b] +hexColor _ = error "Bad color!!!!" + +-- | Helper to make a color from RGB scalar values +makeColor :: Scalar -> Scalar -> Scalar -> Color +makeColor = Color3 + +-- | Where the drawing occurs +drawObject :: YObject -> IO() +drawObject shape = renderPrimitive Triangles $ + mapM_ drawAtom (atoms shape) + +-- simply draw an Atom +drawAtom :: Atom -> IO () +drawAtom atom@(ColoredTriangle (p0,p1,p2,c)) = do + color c + normal $ toGLNormal3 (getNormal atom) + vertex $ toGLVertex3 p0 + vertex $ toGLVertex3 p1 + vertex $ toGLVertex3 p2 + +-- | get the normal vector of an Atom +-- I don't normalize it; it is done by OpenGL +-- in main with 'normalize $= Enabled' +getNormal :: Atom -> Point3D +getNormal (ColoredTriangle (p0,p1,p2,_)) = (p1 - p0) * (p2 - p0) diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/ExtComplex.hs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/ExtComplex.hs new file mode 100644 index 0000000..caba8d0 --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/ExtComplex.hs @@ -0,0 +1,37 @@ +module ExtComplex where + +import Graphics.Rendering.OpenGL + +-- This time I use unpacked strict data type +-- Far faster when compiled. +data ExtComplex = C {-# UNPACK #-} !GLfloat + {-# UNPACK #-} !GLfloat + {-# UNPACK #-} !GLfloat + deriving (Show,Eq) + +instance Num ExtComplex where + -- The shape of the 3D mandelbrot + -- will depend on this formula + (C x y z) * (C x' y' z') = C (x*x' - y*y' - z*z') + (x*y' + y*x' + z*z') + (x*z' + z*x' ) + -- The rest is straightforward + fromInteger n = C (fromIntegral n) 0 0 + (C x y z) + (C x' y' z') = C (x+x') (y+y') (z+z') + abs (C x y z) = C (sqrt (x*x + y*y + z*z)) 0 0 + signum (C x y z) = C (signum x) (signum y) (signum z) + +extcomplex :: GLfloat -> GLfloat -> GLfloat -> ExtComplex +extcomplex x y z = C x y z + +real :: ExtComplex -> GLfloat +real (C x _ _) = x + +im :: ExtComplex -> GLfloat +im (C _ y _) = y + +strange :: ExtComplex -> GLfloat +strange (C _ _ z) = z + +magnitude :: ExtComplex -> GLfloat +magnitude = real.abs diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandel.hs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandel.hs new file mode 100644 index 0000000..7c1ef34 --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandel.hs @@ -0,0 +1,14 @@ +-- The Mandelbrot function +module Mandel (mandel) where + +import ExtComplex + +mandel :: Float -> Float -> Float -> Int -> Int +mandel r i s nbIterations = + f (extcomplex r i s) 0 nbIterations + where + f :: ExtComplex -> ExtComplex -> Int -> Int + f _ _ 0 = 0 + f c z n = if (magnitude z > 2 ) + then n + else f c ((z*z)+c) (n-1) diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandelbulb.lhs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandelbulb.lhs new file mode 100644 index 0000000..90b9e04 --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandelbulb.lhs @@ -0,0 +1,220 @@ + ## Optimization + +Our code architecture feel very clean. +All the meaningful code is in our main file and all display details are +externalized. +If you read the code of `YGL.hs`, you'll see I didn't made everything perfect. +For example, I didn't finished the code of the lights. +But I believe it is a good first step and it will be easy to go further. +Unfortunately the program of the preceding session is extremely slow. +We compute the Mandelbulb for each frame now. + +Before our program structure was: + + +Constant Function -> Constant List of Triangles -> Display + + +Now we have + + +Main loop -> World -> Function -> List of Objects -> Atoms -> Display + + +The World state could change. +The compiler can no more optimize the computation for us. +We have to manually explain when to redraw the shape. + +To optimize we must do some things in a lower level. +Mostly the program remains the same, +but it will provide the list of atoms directly. + +
    + +> import YGL -- Most the OpenGL Boilerplate +> import Mandel -- The 3D Mandelbrot maths +> +> -- Centralize all user input interaction +> inputActionMap :: InputMap World +> inputActionMap = inputMapFromList [ +> (Press ' ' , switchRotation) +> ,(Press 'k' , rotate xdir 5) +> ,(Press 'i' , rotate xdir (-5)) +> ,(Press 'j' , rotate ydir 5) +> ,(Press 'l' , rotate ydir (-5)) +> ,(Press 'o' , rotate zdir 5) +> ,(Press 'u' , rotate zdir (-5)) +> ,(Press 'f' , translate xdir 0.1) +> ,(Press 's' , translate xdir (-0.1)) +> ,(Press 'e' , translate ydir 0.1) +> ,(Press 'd' , translate ydir (-0.1)) +> ,(Press 'z' , translate zdir 0.1) +> ,(Press 'r' , translate zdir (-0.1)) +> ,(Press '+' , zoom 1.1) +> ,(Press '-' , zoom (1/1.1)) +> ,(Press 'h' , resize 2.0) +> ,(Press 'g' , resize (1/2.0)) +> ] + +
    + +> data World = World { +> angle :: Point3D +> , anglePerSec :: Scalar +> , scale :: Scalar +> , position :: Point3D +> , box :: Box3D +> , told :: Time +> -- We replace shape by cache +> , cache :: [YObject] +> } + + +> instance DisplayableWorld World where +> winTitle _ = "The YGL Mandelbulb" +> camera w = Camera { +> camPos = position w, +> camDir = angle w, +> camZoom = scale w } +> -- We update our objects instanciation +> objects = cache + +
    + +> xdir :: Point3D +> xdir = makePoint3D (1,0,0) +> ydir :: Point3D +> ydir = makePoint3D (0,1,0) +> zdir :: Point3D +> zdir = makePoint3D (0,0,1) +> +> rotate :: Point3D -> Scalar -> World -> World +> rotate dir angleValue world = +> world { +> angle = angle world + (angleValue -*< dir) } +> +> switchRotation :: World -> World +> switchRotation world = +> world { +> anglePerSec = if anglePerSec world > 0 then 0 else 5.0 } +> +> translate :: Point3D -> Scalar -> World -> World +> translate dir len world = +> world { +> position = position world + (len -*< dir) } +> +> zoom :: Scalar -> World -> World +> zoom z world = world { +> scale = z * scale world } + +> main :: IO () +> main = yMainLoop inputActionMap idleAction initialWorld + +
    + +Our initial world state is slightly changed: + +> -- We initialize the world state +> -- then angle, position and zoom of the camera +> -- And the shape function +> initialWorld :: World +> initialWorld = World { +> angle = makePoint3D (30,30,0) +> , anglePerSec = 5.0 +> , position = makePoint3D (0,0,0) +> , scale = 1.0 +> , box = Box3D { minPoint = makePoint3D (0-eps, 0-eps, 0-eps) +> , maxPoint = makePoint3D (0+eps, 0+eps, 0+eps) +> , resolution = 0.02 } +> , told = 0 +> -- We declare cache directly this time +> , cache = objectFunctionFromWorld initialWorld +> } +> where eps=2 + +The use of `eps` is a hint to make a better zoom by computing with the right bounds. + +We use the `YGL.getObject3DFromShapeFunction` function directly. +This way instead of providing `XYFunc`, we provide directly a list of Atoms. + +> objectFunctionFromWorld :: World -> [YObject] +> objectFunctionFromWorld w = [Atoms atomList] +> where atomListPositive = +> getObject3DFromShapeFunction +> (shapeFunc (resolution (box w))) (box w) +> atomList = atomListPositive ++ +> map negativeTriangle atomListPositive +> negativeTriangle (ColoredTriangle (p1,p2,p3,c)) = +> ColoredTriangle (negz p1,negz p3,negz p2,c) +> where negz (P (x,y,z)) = P (x,y,-z) + +We know that resize is the only world change that necessitate to +recompute the list of atoms (triangles). +Then we update our world state accordingly. + +> resize :: Scalar -> World -> World +> resize r world = +> tmpWorld { cache = objectFunctionFromWorld tmpWorld } +> where +> tmpWorld = world { box = (box world) { +> resolution = sqrt ((resolution (box world))**2 * r) }} + +All the rest is exactly the same. + +
    + +> idleAction :: Time -> World -> World +> idleAction tnew world = +> world { +> angle = angle world + (delta -*< zdir) +> , told = tnew +> } +> where +> delta = anglePerSec world * elapsed / 1000.0 +> elapsed = fromIntegral (tnew - (told world)) +> +> shapeFunc :: Scalar -> Function3D +> shapeFunc res x y = +> let +> z = maxZeroIndex (ymandel x y) 0 1 20 +> in +> if and [ maxZeroIndex (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 | +> val <- [res], xeps <- [-val,val], yeps<-[-val,val]] +> then Nothing +> else Just (z,colorFromValue 0) +> +> colorFromValue :: Point -> Color +> colorFromValue n = +> let +> t :: Point -> Scalar +> t i = 0.0 + 0.5*cos( i /10 ) +> in +> makeColor (t n) (t (n+5)) (t (n+10)) +> +> -- given f min max nbtest, +> -- considering +> -- - f is an increasing function +> -- - f(min)=0 +> -- - f(max)≠0 +> -- then maxZeroIndex f min max nbtest returns x such that +> -- f(x - ε)=0 and f(x + ε)≠0 +> -- where ε=(max-min)/2^(nbtest+1) +> maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => +> (a -> b) -> a -> a -> Int -> a +> maxZeroIndex _ minval maxval 0 = (minval+maxval)/2 +> maxZeroIndex func minval maxval n = +> if func medpoint /= 0 +> then maxZeroIndex func minval medpoint (n-1) +> else maxZeroIndex func medpoint maxval (n-1) +> where medpoint = (minval+maxval)/2 +> +> ymandel :: Point -> Point -> Point -> Point +> ymandel x y z = fromIntegral (mandel x y z 64) / 64 + +
    + +And you can also consider minor changes in the `YGL.hs` source file. + +- [`YGL.hs`](code/06_Mandelbulb/YGL.hs), the 3D rendering framework +- [`Mandel`](code/06_Mandelbulb/Mandel.hs), the mandel function +- [`ExtComplex`](code/06_Mandelbulb/ExtComplex.hs), the extended complexes diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/YGL.hs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/YGL.hs new file mode 100644 index 0000000..ee70ae7 --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/YGL.hs @@ -0,0 +1,385 @@ +{- +The module YGL will contains most boilerplate +And display details. + +To make things even nicer, we should separate +this file in many different parts. +Typically separate the display function. + +-} +module YGL ( + -- Here is declared our interface with external files + -- that will include our YGL module + + -- Declarations related to data types + Point -- the 1 dimension point type + , Time -- the type for the time + , Scalar -- the type for scalar values + , Color -- the type for color (3 scalars) + , Point3D (..) -- A 3D point type (3 Points) + , makePoint3D -- helper (x,y,z) -> Point3D + , (-*<) -- scalar product on Point3D a -*< (x,y,z) = (ax,ay,az) + , Function3D -- Point -> Point -> Maybe (Point,Color) + , xpoint, ypoint, zpoint + , Atom (..) -- The Atom object (colored triangles for now) + + -- Your world state must be an instance + -- of the DisplayableWorld type class + , DisplayableWorld (..) + -- Datas related to DisplayableWorld + , Camera (..) + , YObject (..) -- 3D Objects to display + , Box3D (..) -- Some bounded 3D box + , getObject3DFromShapeFunction + , makeBox -- helper to make a box + , hexColor -- Color from hexadecimal string + , makeColor -- make color from RGB values + + -- Interface related to user input + , InputMap + , UserInput (Press,Ctrl,Alt,CtrlAlt) + , inputMapFromList + + -- The main loop function to call + , yMainLoop +) where + +-- A bunch of imports +import Numeric (readHex) -- to read hexadecimal values + +-- Import of OpenGL and GLUT +-- but, I use my own Color type, therefore I hide the definition +-- of Color inside GLUT and OpenGL packages +import Graphics.Rendering.OpenGL hiding (Color) +import Graphics.UI.GLUT hiding (Color) +import Data.IORef + +-- I use Map to deal with user interaction +import qualified Data.Map as Map + +-- Some standard stuff +import Control.Monad (when) +import Data.Maybe (isNothing) + +{-- Things start to be complex here. +- Just take the time to follow me. +--} + +-- | A 1D point +type Point = GLfloat +-- | A Scalar value +type Scalar = GLfloat +-- | The time type (currently its Int) +type Time = Int +-- | A 3D Point mainly '(x,y,z)' +data Point3D = P (Point,Point,Point) deriving (Eq,Show,Read) +type Color = Color3 Scalar + +-- Get x (resp. y, z) coordinate of a 3D point +xpoint :: Point3D -> Point +xpoint (P (x,_,_)) = x +ypoint :: Point3D -> Point +ypoint (P (_,y,_)) = y +zpoint :: Point3D -> Point +zpoint (P (_,_,z)) = z + +-- Create a Point3D element from a triplet +makePoint3D :: (Point,Point,Point) -> Point3D +makePoint3D = P + +-- Make Point3D an instance of Num +instance Num Point3D where + (+) (P (ax,ay,az)) (P (bx,by,bz)) = P (ax+bx,ay+by,az+bz) + (-) (P (ax,ay,az)) (P (bx,by,bz)) = P (ax-bx,ay-by,az-bz) + (*) (P (ax,ay,az)) (P (bx,by,bz)) = P ( ay*bz - az*by + , az*bx - ax*bz + , ax*by - ay*bx ) + abs (P (x,y,z)) = P (abs x,abs y, abs z) + signum (P (x,y,z)) = P (signum x, signum y, signum z) + fromInteger i = P (fromInteger i, 0, 0) + +-- The scalar product +infixr 5 -*< +(-*<) :: Scalar -> Point3D -> Point3D +(-*<) s p = P (s*xpoint p, s*ypoint p, s*zpoint p) + +-- Used internally to convert point3D to different types +toGLVector3 :: Point3D -> Vector3 GLfloat +toGLVector3 (P(x,y,z)) = Vector3 x y z + +toGLVertex3 :: Point3D -> Vertex3 GLfloat +toGLVertex3 (P(x,y,z)) = Vertex3 x y z + +toGLNormal3 :: Point3D -> Normal3 GLfloat +toGLNormal3 (P(x,y,z)) = Normal3 x y z + +-- | The Box3D type represent a 3D bounding box +-- | Note if minPoint = (x,y,z) and maxPoint = (x',y',z') +-- | Then to have a non empty box you must have +-- | x (Point,Point,Point) -> Scalar -> Box3D +makeBox mini maxi res = Box3D { + minPoint = makePoint3D mini + , maxPoint = makePoint3D maxi + , resolution = res } + +-- | A Triangle3D is simply 3 points and a color +type Triangle3D = (Point3D,Point3D,Point3D,Color) + +-- | The type Atom is the atom for our display here we'll only use triangles. +-- | For a general purpose library we should add many other different atoms +-- | corresponding to Quads for example. +data Atom = ColoredTriangle Triangle3D + +-- | A Function3D is simply a function for each x,y associate a z and a color +-- | If undefined at point (x,y), it returns Nothing. +type Function3D = Point -> Point -> Maybe (Point,Color) + +-- | Our objects that will be displayed +-- | Wether a function3D delimited by a Box +-- | or a list of Atoms +data YObject = XYFunc Function3D Box3D + | Atoms [Atom] + +-- | The function atoms retrieve the list of atoms from an YObject +atoms :: YObject -> [Atom] +atoms (XYFunc f b) = getObject3DFromShapeFunction f b +atoms (Atoms atomList) = atomList + +-- | We decalre the input map type we need here +-- | It is our API +-- | I don't use Mouse but it can be easily added +type InputMap worldType = Map.Map UserInput (worldType -> worldType) +data UserInput = Press Char | Ctrl Char | Alt Char | CtrlAlt Char + deriving (Eq,Ord,Show,Read) + +-- | A displayable world is a type for which +-- | ther exists a function that provide sufficient informations +-- | to provide a camera, lights, objects and a window title. +class DisplayableWorld world where + camera :: world -> Camera + camera _ = defaultCamera + lights :: world -> [Light] + lights _ = [] + objects :: world -> [YObject] + objects _ = [] + winTitle :: world -> String + winTitle _ = "YGL" + +-- | the Camera type to know how to +-- | Transform the scene to see the right view. +data Camera = Camera { + camPos :: Point3D + , camDir :: Point3D + , camZoom :: Scalar } + +-- | A default initial camera +defaultCamera :: Camera +defaultCamera = Camera { + camPos = makePoint3D (0,0,0) + , camDir = makePoint3D (0,0,0) + , camZoom = 1 } + + +-- | Given a shape function and a delimited Box3D +-- | return a list of Atoms (here only colored triangles) to be displayed +getObject3DFromShapeFunction :: Function3D -> Box3D -> [Atom] +getObject3DFromShapeFunction shape box = do + x <- [xmin,xmin+res..xmax] + y <- [ymin,ymin+res..ymax] + let + neighbors = [(x,y),(x+res,y),(x+res,y+res),(x,y+res)] + -- zs are 3D points with found depth and color + -- zs :: [ (Point,Point,Point,Maybe (Point,Color) ] + zs = map (\(u,v) -> (u,v,shape u v)) neighbors + -- ps are 3D opengl points + color value + ps = zs + -- If the point diverged too fast, don't display it + if any (\(_,_,z) -> isNothing z) zs + then [] + -- Draw two triangles + -- 3 - 2 + -- | / | + -- 0 - 1 + -- The order is important + else + [ makeAtom (ps!!0) (ps!!2) (ps!!1) + , makeAtom (ps!!0) (ps!!3) (ps!!2) ] + where + makeAtom (p0x,p0y,Just (p0z,c0)) (p1x,p1y,Just (p1z,_)) (p2x,p2y,Just (p2z,_)) = + ColoredTriangle (makePoint3D (p0x,p0y,p0z) + ,makePoint3D (p1x,p1y,p1z) + ,makePoint3D (p2x,p2y,p2z) + ,c0) + makeAtom _ _ _ = error "Somethings wrong here" + + -- some naming to make it + -- easier to read + xmin = xpoint $ minPoint box + xmax = xpoint $ maxPoint box + ymin = ypoint $ minPoint box + ymax = ypoint $ maxPoint box + res = resolution box + +-- | Get the user input map from a list +inputMapFromList :: (DisplayableWorld world) => + [(UserInput,world -> world)] -> InputMap world +inputMapFromList = Map.fromList + +{-- +- We set our mainLoop function +- As you can see the code is _not_ pure +- and not even functionnal friendly! +- But when called, +- it will look like a pure functional function. +--} +yMainLoop :: (DisplayableWorld worldType) => + -- the mapping user input / world + InputMap worldType + -- function that modify the world + -> (Time -> worldType -> worldType) + -- the world state of type worldType + -> worldType + -- into IO () for obvious reason + -> IO () +yMainLoop inputActionMap + worldTranformer + world = do + -- The boilerplate + _ <- getArgsAndInitialize + initialDisplayMode $= + [WithDepthBuffer,DoubleBuffered,RGBMode] + _ <- createWindow $ winTitle world + depthFunc $= Just Less + windowSize $= Size 500 500 + -- The state variables for the world (I know it feels BAD) + worldRef <- newIORef world + -- Action to call when waiting + idleCallback $= Just (idle worldTranformer worldRef) + -- the keyboard will update the world + keyboardMouseCallback $= + Just (keyboardMouse inputActionMap worldRef) + -- We generate one frame using the callback + displayCallback $= display worldRef + -- let OpenGL resize normal vectors to unity + normalize $= Enabled + shadeModel $= Smooth + -- Lights (in a better version should be put elsewhere) + lighting $= Enabled + ambient (Light 0) $= Color4 0.5 0.5 0.5 1 + diffuse (Light 0) $= Color4 1 1 1 1 + light (Light 0) $= Enabled + pointSmooth $= Enabled + + colorMaterial $= Just (Front,AmbientAndDiffuse) + materialAmbient Front $= Color4 0.0 0.0 0.0 1 + materialDiffuse Front $= Color4 0.0 0.0 0.0 1 + materialSpecular Front $= Color4 1 1 1 1 + materialEmission Front $= Color4 0.0 0.0 0.0 1 + materialShininess Front $= 96 + -- We enter the main loop + mainLoop + +-- When no user input entered do nothing +idle :: (Time -> worldType -> worldType) -> IORef worldType -> IO () +idle worldTranformer world = do + w <- get world + t <- get elapsedTime + world $= worldTranformer t w + postRedisplay Nothing + +-- | Get User Input +-- | both cleaner, terser and more expendable than the preceeding code +keyboardMouse :: InputMap a -> IORef a + -> Key -> KeyState -> Modifiers -> Position -> IO() +keyboardMouse input world key state _ _ = + when (state == Down) $ + let + charFromKey (Char c) = c + -- To complete if you want to finish it + charFromKey _ = '#' + + transformator = Map.lookup (Press (charFromKey key)) input + in + mayTransform transformator + where + mayTransform Nothing = return () + mayTransform (Just transform) = do + w <- get world + world $= transform w + + +-- | The function that will display datas +display :: (HasGetter g, DisplayableWorld world) => + g world -> IO () +display worldRef = do + -- BEWARE UGLINESS!!!! + -- SHOULD NEVER MODIFY worldRef HERE!!!! + -- + -- I SAID NEVER. + w <- get worldRef + -- NO REALLY, NEVER!!!! + -- If someone write a line starting by + -- w $= ... Shoot him immediately in the head + -- and refere to competent authorities + let cam = camera w + -- set the background color (dark solarized theme) + -- Could also be externalized to world state + clearColor $= Color4 0 0.1686 0.2117 1 + clear [ColorBuffer,DepthBuffer] + -- Transformation to change the view + loadIdentity -- reset any transformation + -- tranlate + translate $ toGLVector3 (camPos cam) + -- zoom + scale (camZoom cam) (camZoom cam) (camZoom cam) + -- rotate + rotate (xpoint (camDir cam)) $ Vector3 1.0 0.0 (0.0::GLfloat) + rotate (ypoint (camDir cam)) $ Vector3 0.0 1.0 (0.0::GLfloat) + rotate (zpoint (camDir cam)) $ Vector3 0.0 0.0 (1.0::GLfloat) + -- Now that all transformation were made + -- We create the object(s) + _ <- preservingMatrix $ mapM drawObject (objects w) + swapBuffers -- refresh screen + +-- Hexa style colors +scalarFromHex :: String -> Scalar +scalarFromHex = (/256) . fst . head . readHex + +-- | Color from CSS style color string +hexColor :: String -> Color +hexColor ('#':rd:ru:gd:gu:bd:bu:[]) = Color3 (scalarFromHex [rd,ru]) + (scalarFromHex [gd,gu]) + (scalarFromHex [bd,bu]) +hexColor ('#':r:g:b:[]) = hexColor ['#',r,r,g,g,b,b] +hexColor _ = error "Bad color!!!!" + +-- | Helper to make a color from RGB scalar values +makeColor :: Scalar -> Scalar -> Scalar -> Color +makeColor = Color3 + +-- | Where the drawing occurs +drawObject :: YObject -> IO() +drawObject shape = renderPrimitive Triangles $ + mapM_ drawAtom (atoms shape) + +-- simply draw an Atom +drawAtom :: Atom -> IO () +drawAtom atom@(ColoredTriangle (p0,p1,p2,c)) = do + color c + normal $ toGLNormal3 (getNormal atom) + vertex $ toGLVertex3 p0 + vertex $ toGLVertex3 p1 + vertex $ toGLVertex3 p2 + +-- | get the normal vector of an Atom +-- I don't normalize it; it is done by OpenGL +-- in main with 'normalize $= Enabled' +getNormal :: Atom -> Point3D +getNormal (ColoredTriangle (p0,p1,p2,_)) = (p1 - p0) * (p2 - p0) diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/07_Conclusion/Conclusion.lhs b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/07_Conclusion/Conclusion.lhs new file mode 100644 index 0000000..d222d3f --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/07_Conclusion/Conclusion.lhs @@ -0,0 +1,25 @@ + ## Conclusion + +As we can use imperative style in a functional language, +know you can use functional style in imperative languages. +This article exposed a way to organize some code in a functional way. +I'd like to stress the usage of Haskell made it very simple to achieve this. + +Once you are used to pure functional style, +it is hard not to see all advantages it offers. + +The code in the two last sections is completely pure and functional. +Furthermore I don't use `GLfloat`, `Color3` or any other OpenGL type. +If I want to use another library in the future, +I would be able to keep all the pure code and simply update the YGL module. + +The `YGL` module can be seen as a "wrapper" around 3D display and user interaction. +It is a clean separator between the imperative paradigm and functional paradigm. + +If you want to go further, it shouldn't be hard to add parallelism. +This should be easy mainly because most of the visible code is pure. +Such an optimization would have been harder by using directly the OpenGL library. + +You should also want to make a more precise object. Because, the Mandelbulb is +clearly not convex. But a precise rendering might be very long from +O(n².log(n)) to O(n³). diff --git a/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/index.html b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/index.html new file mode 100644 index 0000000..e122ebb --- /dev/null +++ b/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/index.html @@ -0,0 +1,1307 @@ + + + + + + YBlog - Haskell Progressive Example + + + + + + + + + + + + + +
    + + +
    +

    Haskell Progressive Example

    +
    +
    +
    +
    +

    The B in Benoît B. Mandelbrot stand for Benoît B. Mandelbrot

    +
    + +

    tl;dr: A progressive Haskell example. A Mandelbrot set extended in 3D, rendered using OpenGL and coded with Haskell. In the end the code will be very clean. The significant stuff will be in a pure functional bubble. The display details will be put in an external module playing the role of a wrapper. Imperative language could also benefit from this functional organization.

    +
    + +

    Introduction

    +

    In my preceding article I introduced Haskell.

    +

    This article goes further. It will show how to use functional programming with interactive programs. But more than that, it will show how to organize your code in a functional way. This article is more about functional paradigm than functional language. The code organization can be used in most imperative language.

    +

    As Haskell is designed for functional paradigm, it is easier to use in this context. In reality, the firsts sections will use an imperative paradigm. As you can use functional paradigm in imperative language, you can also use imperative paradigm in functional languages.

    +

    This article is about creating an useful and clean program. It can interact with the user in real time. It uses OpenGL, a library with imperative programming foundations. Despite this fact, most of the final code will remain in the pure part (no IO).

    +

    I believe the main audience for this article are:

    +
      +
    • Haskell programmer looking for an OpengGL tutorial.
    • +
    • People interested in program organization (programming language agnostic).
    • +
    • Fractal lovers and in particular 3D fractal.
    • +
    • People interested in user interaction in a functional paradigm.
    • +
    +

    I had in mind for some time now to make a Mandelbrot set explorer. I had already written a command line Mandelbrot set generator in Haskell. This utility is highly parallel; it uses the repa package1.

    +

    This time, we will not parallelize the computation. Instead, we will display the Mandelbrot set extended in 3D using OpenGL and Haskell. You will be able to move it using your keyboard. This object is a Mandelbrot set in the plan (z=0), and something nice to see in 3D.

    +

    Here are some screenshots of the result:

    +
    +The entire Mandelbulb +
    +The entire Mandelbulb +
    +
    +A Mandelbulb detail +
    +A Mandelbulb detail +
    +
    +Another detail of the Mandelbulb +
    +Another detail of the Mandelbulb +
    + +

    And you can see the intermediate steps to reach this goal:

    +

    The parts of the article

    +

    From the 2nd section to the 4th it will be dirtier and dirtier. We start cleaning the code at the 5th section.

    +
    +

    Download the source code of this section → 01_Introduction/hglmandel.lhs

    +

    First version

    +

    We can consider two parts. The first being mostly some boilerplate2. And the second part more focused on OpenGL and content.

    +

    Let’s play the song of our people

    +
    +
    import Graphics.Rendering.OpenGL
    +import Graphics.UI.GLUT
    +import Data.IORef
    +
    + +

    For efficiency reason3, I will not use the default Haskell Complex data type.

    +
    +
    data Complex = C (Float,Float) deriving (Show,Eq)
    +
    + +
    +
    instance Num Complex where
    +    fromInteger n = C (fromIntegral n,0.0)
    +    C (x,y) * C (z,t) = C (z*x - y*t, y*z + x*t)
    +    C (x,y) + C (z,t) = C (x+z, y+t)
    +    abs (C (x,y))     = C (sqrt (x*x + y*y),0.0)
    +    signum (C (x,y))  = C (signum x , 0.0)
    +
    + +

    We declare some useful functions for manipulating complex numbers:

    +
    +
    complex :: Float -> Float -> Complex
    +complex x y = C (x,y)
    +
    +real :: Complex -> Float
    +real (C (x,y))    = x
    +
    +im :: Complex -> Float
    +im   (C (x,y))    = y
    +
    +magnitude :: Complex -> Float
    +magnitude = real.abs
    +
    + +

    Let us start

    +

    We start by giving the main architecture of our program:

    +
    +
    main :: IO ()
    +main = do
    +  -- GLUT need to be initialized
    +  (progname,_) <- getArgsAndInitialize
    +  -- We will use the double buffered mode (GL constraint)
    +  initialDisplayMode $= [DoubleBuffered]
    +  -- We create a window with some title
    +  createWindow "Mandelbrot Set with Haskell and OpenGL"
    +  -- Each time we will need to update the display
    +  -- we will call the function 'display'
    +  displayCallback $= display
    +  -- We enter the main loop
    +  mainLoop
    +
    + +

    Mainly, we initialize our OpenGL application. We declared that the function display will be used to render the graphics:

    +
    +
    display = do
    +  clear [ColorBuffer] -- make the window black
    +  loadIdentity -- reset any transformation
    +  preservingMatrix drawMandelbrot
    +  swapBuffers -- refresh screen
    +
    + +

    Also here, there is only one interesting line; the draw will occur in the function drawMandelbrot.

    +

    This function will provide a list of draw actions. Remember that OpenGL is imperative by design. Then, one of the consequence is you must write the actions in the right order. No easy parallel drawing here. Here is the function which will render something on the screen:

    +
    +
    drawMandelbrot =
    +  -- We will print Points (not triangles for example) 
    +  renderPrimitive Points $ do
    +    mapM_ drawColoredPoint allPoints
    +  where
    +      drawColoredPoint (x,y,c) = do
    +          color c -- set the current color to c
    +          -- then draw the point at position (x,y,0)
    +          -- remember we're in 3D
    +          vertex $ Vertex3 x y 0 
    +
    + +

    The mapM_ function is mainly the same as map but inside a monadic context. More precisely, this can be transformed as a list of actions where the order is important:

    +
    drawMandelbrot = 
    +  renderPrimitive Points $ do
    +    color color1
    +    vertex $ Vertex3 x1 y1 0
    +    ...
    +    color colorN
    +    vertex $ Vertex3 xN yN 0
    +

    We also need some kind of global variables. In fact, global variable are a proof of a design problem. We will get rid of them later.

    +
    +
    width = 320 :: GLfloat
    +height = 320 :: GLfloat
    +
    + +

    And of course our list of colored points. In OpenGL the default coordinate are from -1 to 1.

    +
    +
    allPoints :: [(GLfloat,GLfloat,Color3 GLfloat)]
    +allPoints = [ (x/width,y/height,colorFromValue $ mandel x y) | 
    +                  x <- [-width..width], 
    +                  y <- [-height..height]]
    +
    + +

    We need a function which transform an integer value to some color:

    +
    +
    colorFromValue n =
    +  let 
    +      t :: Int -> GLfloat
    +      t i = 0.5 + 0.5*cos( fromIntegral i / 10 )
    +  in
    +    Color3 (t n) (t (n+5)) (t (n+10))
    +
    + +

    And now the mandel function. Given two coordinates in pixels, it returns some integer value:

    +
    +
    mandel x y = 
    +  let r = 2.0 * x / width
    +      i = 2.0 * y / height
    +  in
    +      f (complex r i) 0 64
    +
    + +

    It uses the main Mandelbrot function for each complex \(c\). The Mandelbrot set is the set of complex number \(c\) such that the following sequence does not escape to infinity.

    +

    Let us define \(f_c: \)

    +


    fc(z) = z2 + c

    +

    The sequence is:

    +


    0 → fc(0) → fc(fc(0)) → ⋯ → fcn(0) → ⋯

    +

    Of course, instead of trying to test the real limit, we just make a test after a finite number of occurrences.

    +
    +
    f :: Complex -> Complex -> Int -> Int
    +f c z 0 = 0
    +f c z n = if (magnitude z > 2 ) 
    +          then n
    +          else f c ((z*z)+c) (n-1)
    +
    + +

    Well, if you download this file (look at the bottom of this section), compile it and run it this is the result:

    +

    The mandelbrot set version 1

    +

    A first very interesting property of this program is that the computation for all the points is done only once. It is a bit long before the first image appears, but if you resize the window, it updates instantaneously. This property is a direct consequence of purity. If you look closely, you see that allPoints is a pure list. Therefore, calling allPoints will always render the same result and Haskell is clever enough to use this property. While Haskell doesn’t garbage collect allPoints the result is reused for free. We did not specified this value should be saved for later use. It is saved for us.

    +

    See what occurs if we make the window bigger:

    +

    The mandelbrot too wide, black lines and columns

    +

    We see some black lines because we have drawn less point than there is on the surface. We can repair this by drawing little squares instead of just points. But, instead we will do something a bit different and unusual.

    +

    Download the source code of this section → 01_Introduction/hglmandel.lhs

    +
    +

    Download the source code of this section → 02_Edges/HGLMandelEdge.lhs

    +

    Only the edges

    +
    + +
    +
    import Graphics.Rendering.OpenGL
    +import Graphics.UI.GLUT
    +import Data.IORef
    +-- Use UNPACK data because it is faster
    +-- The ! is for strict instead of lazy
    +data Complex = C  {-# UNPACK #-} !Float 
    +                  {-# UNPACK #-} !Float 
    +               deriving (Show,Eq)
    +instance Num Complex where
    +    fromInteger n = C (fromIntegral n) 0.0
    +    (C x y) * (C z t) = C (z*x - y*t) (y*z + x*t)
    +    (C x y) + (C z t) = C (x+z) (y+t)
    +    abs (C x y)     = C (sqrt (x*x + y*y)) 0.0
    +    signum (C x y)  = C (signum x) 0.0
    +complex :: Float -> Float -> Complex
    +complex x y = C x y
    +
    +real :: Complex -> Float
    +real (C x y)    = x
    +
    +im :: Complex -> Float
    +im   (C x y)    = y
    +
    +magnitude :: Complex -> Float
    +magnitude = real.abs
    +main :: IO ()
    +main = do
    +  -- GLUT need to be initialized
    +  (progname,_) <- getArgsAndInitialize
    +  -- We will use the double buffered mode (GL constraint)
    +  initialDisplayMode $= [DoubleBuffered]
    +  -- We create a window with some title
    +  createWindow "Mandelbrot Set with Haskell and OpenGL"
    +  -- Each time we will need to update the display
    +  -- we will call the function 'display'
    +  displayCallback $= display
    +  -- We enter the main loop
    +  mainLoop
    +display = do
    +   -- set the background color (dark solarized theme)
    +  clearColor $= Color4 0 0.1686 0.2117 1
    +  clear [ColorBuffer] -- make the window black
    +  loadIdentity -- reset any transformation
    +  preservingMatrix drawMandelbrot
    +  swapBuffers -- refresh screen
    +
    +width = 320 :: GLfloat
    +height = 320 :: GLfloat
    +
    + +
    + +

    This time, instead of drawing all points, we will simply draw the edges of the Mandelbrot set. The method I use is a rough approximation. I consider the Mandelbrot set to be almost convex. The result will be good enough for the purpose of this tutorial.

    +

    We change slightly the drawMandelbrot function. We replace the Points by LineLoop

    +
    +
    drawMandelbrot =
    +  -- We will print Points (not triangles for example) 
    +  renderPrimitive LineLoop $ do
    +    mapM_ drawColoredPoint allPoints
    +  where
    +      drawColoredPoint (x,y,c) = do
    +          color c -- set the current color to c
    +          -- then draw the point at position (x,y,0)
    +          -- remember we're in 3D
    +          vertex $ Vertex3 x y 0 
    +
    + +

    And now, we should change our list of points. Instead of drawing every point of the visible surface, we will choose only point on the surface.

    +
    +
    allPoints = positivePoints ++ 
    +      map (\(x,y,c) -> (x,-y,c)) (reverse positivePoints)
    +
    + +

    We only need to compute the positive point. The Mandelbrot set is symmetric relatively to the abscisse axis.

    +
    +
    positivePoints :: [(GLfloat,GLfloat,Color3 GLfloat)]
    +positivePoints = do
    +     x <- [-width..width]
    +     let y = maxZeroIndex (mandel x) 0 height (log2 height)
    +     if y < 1 -- We don't draw point in the absciss
    +        then []
    +        else return (x/width,y/height,colorFromValue $ mandel x y)
    +     where
    +         log2 n = floor ((log n) / log 2)
    +
    + +

    This function is interesting. For those not used to the list monad here is a natural language version of this function:

    +
    positivePoints =
    +    for all x in the range [-width..width]
    +    let y be smallest number s.t. mandel x y > 0
    +    if y is on 0 then don't return a point
    +    else return the value corresonding to (x,y,color for (x+iy))
    +

    In fact using the list monad you write like if you consider only one element at a time and the computation is done non deterministically. To find the smallest number such that mandel x y > 0 we use a simple dichotomy:

    +
    +
    -- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex func minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if (func medpoint) /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    + +

    No rocket science here. See the result now:

    +

    The edges of the mandelbrot set

    +
    + +
    +
    colorFromValue n =
    +  let 
    +      t :: Int -> GLfloat
    +      t i = 0.5 + 0.5*cos( fromIntegral i / 10 )
    +  in
    +    Color3 (t n) (t (n+5)) (t (n+10))
    +
    + +
    +
    mandel x y = 
    +  let r = 2.0 * x / width
    +      i = 2.0 * y / height
    +  in
    +      f (complex r i) 0 64
    +
    + +
    +
    f :: Complex -> Complex -> Int -> Int
    +f c z 0 = 0
    +f c z n = if (magnitude z > 2 ) 
    +          then n
    +          else f c ((z*z)+c) (n-1)
    +
    + +
    + +

    Download the source code of this section → 02_Edges/HGLMandelEdge.lhs

    +
    +

    Download the source code of this section → 03_Mandelbulb/Mandelbulb.lhs

    +

    3D Mandelbrot?

    +

    Now we will we extend to a third dimension. But, there is no 3D equivalent to complex. In fact, the only extension known are quaternions (in 4D). As I know almost nothing about quaternions, I will use some extended complex, instead of using a 3D projection of quaternions. I am pretty sure this construction is not useful for numbers. But it will be enough for us to create something that look nice.

    +

    This section is quite long, but don’t be afraid, most of the code is some OpenGL boilerplate. If you just want to skim this section, here is a high level representation:

    +
    +
      +
    • OpenGL Boilerplate

    • +
    • set some IORef (understand variables) for states
    • +
    • Drawing:

      +
        +
      • set doubleBuffer, handle depth, window size…
      • +
      • Use state to apply some transformations
      • +
    • +
    • Keyboard: hitting some key change the state of IORef

    • +
    • Generate 3D Object

    • +
    +

    ~ allPoints :: [ColoredPoint]
    allPoints = for all (x,y), -width 0 add the points (x, y, z,color) (x,-y, z,color) (x, y,-z,color) (x,-y,-z,color) + neighbors to make triangles ~

    +
    +
    + +
    +
    import Graphics.Rendering.OpenGL
    +import Graphics.UI.GLUT
    +import Data.IORef
    +type ColoredPoint = (GLfloat,GLfloat,GLfloat,Color3 GLfloat)
    +
    + +
    + +

    We declare a new type ExtComplex (for extended complex). An extension of complex numbers with a third component:

    +
    +
    data ExtComplex = C (GLfloat,GLfloat,GLfloat) 
    +                  deriving (Show,Eq)
    +instance Num ExtComplex where
    +    -- The shape of the 3D mandelbrot 
    +    -- will depend on this formula
    +    C (x,y,z) * C (x',y',z') = C (x*x' - y*y' - z*z', 
    +                                  x*y' + y*x' + z*z', 
    +                                  x*z' + z*x' )
    +    -- The rest is straightforward
    +    fromInteger n = C (fromIntegral n, 0, 0)
    +    C (x,y,z) + C (x',y',z') = C (x+x', y+y', z+z')
    +    abs (C (x,y,z))     = C (sqrt (x*x + y*y + z*z), 0, 0)
    +    signum (C (x,y,z))  = C (signum x, signum y, signum z)
    +
    + +

    The most important part is the new multiplication instance. Modifying this formula will change radically the shape of the result. Here is the formula written in a more mathematical notation. I called the third component of these extended complex strange.

    +


    real((x, y, z) * (xʹ, yʹ, zʹ)) = xxʹ − yyʹ − zzʹ

    +


    im((x, y, z) * (xʹ, yʹ, zʹ)) = xyʹ − yxʹ + zzʹ

    +


    strange((x, y, z) * (xʹ, yʹ, zʹ)) = xzʹ + zxʹ

    +

    Note how if z=z'=0 then the multiplication is the same to the complex one.

    +
    + +
    +
    extcomplex :: GLfloat -> GLfloat -> GLfloat -> ExtComplex
    +extcomplex x y z = C (x,y,z)
    +
    +real :: ExtComplex -> GLfloat
    +real (C (x,y,z))    = x
    +
    +im :: ExtComplex -> GLfloat
    +im   (C (x,y,z))    = y
    +
    +strange :: ExtComplex -> GLfloat
    +strange (C (x,y,z)) = z
    +
    +magnitude :: ExtComplex -> GLfloat
    +magnitude = real.abs
    +
    + +
    + +

    From 2D to 3D

    +

    As we will use some 3D, we add some new directive in the boilerplate. But mainly, we simply state that will use some depth buffer. And also we will listen the keyboard.

    +
    +
    main :: IO ()
    +main = do
    +  -- GLUT need to be initialized
    +  (progname,_) <- getArgsAndInitialize
    +  -- We will use the double buffered mode (GL constraint)
    +  -- We also Add the DepthBuffer (for 3D)
    +  initialDisplayMode $= 
    +      [WithDepthBuffer,DoubleBuffered,RGBMode]
    +  -- We create a window with some title
    +  createWindow "3D HOpengGL Mandelbrot"
    +  -- We add some directives
    +  depthFunc  $= Just Less
    +  windowSize $= Size 500 500
    +  -- Some state variables (I know it feels BAD)
    +  angle   <- newIORef ((35,0)::(GLfloat,GLfloat))
    +  zoom    <- newIORef (2::GLfloat)
    +  campos  <- newIORef ((0.7,0)::(GLfloat,GLfloat))
    +  -- Function to call each frame
    +  idleCallback $= Just idle
    +  -- Function to call when keyboard or mouse is used
    +  keyboardMouseCallback $= 
    +          Just (keyboardMouse angle zoom campos)
    +  -- Each time we will need to update the display
    +  -- we will call the function 'display'
    +  -- But this time, we add some parameters
    +  displayCallback $= display angle zoom campos
    +  -- We enter the main loop
    +  mainLoop
    +
    + +

    The idle is here to change the states. There should never be any modification done in the display function.

    +
    +
    idle = postRedisplay Nothing
    +
    + +

    We introduce some helper function to manipulate standard IORef. Mainly modVar x f is equivalent to the imperative x:=f(x), modFst (x,y) (+1) is equivalent to (x,y) := (x+1,y) and modSnd (x,y) (+1) is equivalent to (x,y) := (x,y+1)

    +
    +
    modVar v f = do
    +  v' <- get v
    +  v $= (f v')
    +mapFst f (x,y) = (f x,  y)
    +mapSnd f (x,y) = (  x,f y)
    +
    + +

    And we use them to code the function handling keyboard. We will use the keys hjkl to rotate, oi to zoom and sedf to move. Also, hitting space will reset the view. Remember that angle and campos are pairs and zoom is a scalar. Also note (+0.5) is the function \x->x+0.5 and (-0.5) is the number -0.5 (yes I share your pain).

    +
    +
    keyboardMouse angle zoom campos key state modifiers position =
    +  -- We won't use modifiers nor position
    +  kact angle zoom campos key state
    +  where 
    +    -- reset view when hitting space
    +    kact a z p (Char ' ') Down = do
    +          a $= (0,0) -- angle 
    +          z $= 1     -- zoom
    +          p $= (0,0) -- camera position
    +    -- use of hjkl to rotate
    +    kact a _ _ (Char 'h') Down = modVar a (mapFst (+0.5))
    +    kact a _ _ (Char 'l') Down = modVar a (mapFst (+(-0.5)))
    +    kact a _ _ (Char 'j') Down = modVar a (mapSnd (+0.5))
    +    kact a _ _ (Char 'k') Down = modVar a (mapSnd (+(-0.5)))
    +    -- use o and i to zoom
    +    kact _ z _ (Char 'o') Down = modVar z (*1.1)
    +    kact _ z _ (Char 'i') Down = modVar z (*0.9)
    +    -- use sdfe to move the camera
    +    kact _ _ p (Char 's') Down = modVar p (mapFst (+0.1))
    +    kact _ _ p (Char 'f') Down = modVar p (mapFst (+(-0.1)))
    +    kact _ _ p (Char 'd') Down = modVar p (mapSnd (+0.1))
    +    kact _ _ p (Char 'e') Down = modVar p (mapSnd (+(-0.1)))
    +    -- any other keys does nothing
    +    kact _ _ _ _ _ = return ()
    +
    + +

    Note display takes some parameters this time. This function if full of boilerplate:

    +
    +
    display angle zoom position = do
    +   -- set the background color (dark solarized theme)
    +  clearColor $= Color4 0 0.1686 0.2117 1
    +  clear [ColorBuffer,DepthBuffer]
    +  -- Transformation to change the view
    +  loadIdentity -- reset any transformation
    +  -- tranlate
    +  (x,y) <- get position
    +  translate $ Vector3 x y 0 
    +  -- zoom
    +  z <- get zoom
    +  scale z z z
    +  -- rotate
    +  (xangle,yangle) <- get angle
    +  rotate xangle $ Vector3 1.0 0.0 (0.0::GLfloat)
    +  rotate yangle $ Vector3 0.0 1.0 (0.0::GLfloat)
    +
    +  -- Now that all transformation were made
    +  -- We create the object(s)
    +  preservingMatrix drawMandelbrot
    +
    +  swapBuffers -- refresh screen
    +
    + +

    Not much to say about this function. Mainly there are two parts: apply some transformations, draw the object.

    +

    The 3D Mandelbrot

    +

    We have finished with the OpenGL section, let’s talk about how we generate the 3D points and colors. First, we will set the number of details to 200 pixels in the three dimensions.

    +
    +
    nbDetails = 200 :: GLfloat
    +width  = nbDetails
    +height = nbDetails
    +deep   = nbDetails
    +
    + +

    This time, instead of just drawing some line or some group of points, we will show triangles. The function allPoints will provide a multiple of three points. Each three successive point representing the coordinate of each vertex of a triangle.

    +
    +
    drawMandelbrot = do
    +  -- We will print Points (not triangles for example) 
    +  renderPrimitive Triangles $ do
    +    mapM_ drawColoredPoint allPoints
    +  where
    +      drawColoredPoint (x,y,z,c) = do
    +          color c
    +          vertex $ Vertex3 x y z
    +
    + +

    In fact, we will provide six ordered points. These points will be used to draw two triangles.

    +

    Explain triangles

    +

    The next function is a bit long. Here is an approximative English version:

    +
    forall x from -width to width
    +  forall y from -height to height
    +    forall the neighbors of (x,y)
    +      let z be the smalled depth such that (mandel x y z)>0
    +      let c be the color given by mandel x y z 
    +      add the point corresponding to (x,y,z,c)
    +

    Also, I added a test to hide points too far from the border. In fact, this function show points close to the surface of the modified mandelbrot set. But not the mandelbrot set itself.

    +
    depthPoints :: [ColoredPoint]
    +depthPoints = do
    +  x <- [-width..width]
    +  y <- [-height..height]
    +  let 
    +      depthOf x' y' = maxZeroIndex (mandel x' y') 0 deep logdeep 
    +      logdeep = floor ((log deep) / log 2)
    +      z1 = depthOf    x     y
    +      z2 = depthOf (x+1)    y
    +      z3 = depthOf (x+1) (y+1)
    +      z4 = depthOf    x  (y+1)
    +      c1 = mandel    x     y  (z1+1)
    +      c2 = mandel (x+1)    y  (z2+1)
    +      c3 = mandel (x+1) (y+1) (z3+1)
    +      c4 = mandel    x  (y+1) (z4+1)
    +      p1 = (   x /width,   y /height, z1/deep, colorFromValue c1)
    +      p2 = ((x+1)/width,   y /height, z2/deep, colorFromValue c2)
    +      p3 = ((x+1)/width,(y+1)/height, z3/deep, colorFromValue c3)
    +      p4 = (   x /width,(y+1)/height, z4/deep, colorFromValue c4)
    +  if (and $ map (>=57) [c1,c2,c3,c4])
    +  then []
    +  else [p1,p2,p3,p1,p3,p4]
    +

    If you look at the function above, you see a lot of common patterns. Haskell is very efficient to make this better. Here is a harder to read but shorter and more generic rewritten function:

    +
    +
    depthPoints :: [ColoredPoint]
    +depthPoints = do
    +  x <- [-width..width]
    +  y <- [-height..height]
    +  let 
    +    neighbors = [(x,y),(x+1,y),(x+1,y+1),(x,y+1)]
    +    depthOf (u,v) = maxZeroIndex (mandel u v) 0 deep logdeep
    +    logdeep = floor ((log deep) / log 2)
    +    -- zs are 3D points with found depth
    +    zs = map (\(u,v) -> (u,v,depthOf (u,v))) neighbors
    +    -- ts are 3D pixels + mandel value
    +    ts = map (\(u,v,w) -> (u,v,w,mandel u v (w+1))) zs
    +    -- ps are 3D opengl points + color value
    +    ps = map (\(u,v,w,c') -> 
    +        (u/width,v/height,w/deep,colorFromValue c')) ts
    +  -- If the point diverged too fast, don't display it
    +  if (and $ map (\(_,_,_,c) -> c>=57) ts)
    +  then []
    +  -- Draw two triangles
    +  else [ps!!0,ps!!1,ps!!2,ps!!0,ps!!2,ps!!3]
    +
    + +

    If you prefer the first version, then just imagine how hard it will be to change the enumeration of the point from (x,y) to (x,z) for example.

    +

    Also, we didn’t searched for negative values. This modified Mandelbrot is no more symmetric relatively to the plan y=0. But it is symmetric relatively to the plan z=0. Then I mirror these values.

    +
    +
    allPoints :: [ColoredPoint]
    +allPoints = planPoints ++ map inverseDepth  planPoints
    +  where 
    +      planPoints = depthPoints
    +      inverseDepth (x,y,z,c) = (x,y,-z+1/deep,c)
    +
    + +

    The rest of the program is very close to the preceding one.

    +
    + +
    +
    -- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => 
    +                 (a -> b) -> a -> a -> Int -> a
    +maxZeroIndex func minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if (func medpoint) /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    + +

    I made the color slightly brighter

    +
    +
    colorFromValue n =
    +  let 
    +      t :: Int -> GLfloat
    +      t i = 0.7 + 0.3*cos( fromIntegral i / 10 )
    +  in
    +    Color3 (t n) (t (n+5)) (t (n+10))
    +
    + +

    We only changed from Complex to ExtComplex of the main f function.

    +
    +
    f :: ExtComplex -> ExtComplex -> Int -> Int
    +f c z 0 = 0
    +f c z n = if (magnitude z > 2 ) 
    +          then n
    +          else f c ((z*z)+c) (n-1)
    +
    + +
    + +

    We simply add a new dimension to the mandel function and change the type signature of f from Complex to ExtComplex.

    +
    +
    mandel x y z = 
    +  let r = 2.0 * x / width
    +      i = 2.0 * y / height
    +      s = 2.0 * z / deep
    +  in
    +      f (extcomplex r i s) 0 64
    +
    + +

    Here is the result:

    +

    A 3D mandelbrot like

    +

    Download the source code of this section → 03_Mandelbulb/Mandelbulb.lhs

    +
    +

    Download the source code of this section → 04_Mandelbulb/Mandelbulb.lhs

    +

    Naïve code cleaning

    +

    The first approach to clean the code is to separate the GLUT/OpenGL part from the computation of the shape. Here is the cleaned version of the preceding section. Most boilerplate was put in external files.

    + +
    +
    import YBoiler -- Most the OpenGL Boilerplate
    +import Mandel -- The 3D Mandelbrot maths
    +
    + +

    The yMainLoop takes two arguments: the title of the window and a function from time to triangles

    +
    +
    main :: IO ()
    +main = yMainLoop "3D Mandelbrot" (\_ -> allPoints)
    +
    + +

    We set some global constant (this is generally bad).

    +
    +
    nbDetails = 200 :: GLfloat
    +width  = nbDetails
    +height = nbDetails
    +deep   = nbDetails
    +
    + +

    We then generate colored points from our function. This is similar to the preceding section.

    +
    +
    allPoints :: [ColoredPoint]
    +allPoints = planPoints ++ map inverseDepth  planPoints
    +  where 
    +      planPoints = depthPoints ++ map inverseHeight depthPoints
    +      inverseHeight (x,y,z,c) = (x,-y,z,c)
    +      inverseDepth (x,y,z,c) = (x,y,-z+1/deep,c)
    +
    + +
    +
    depthPoints :: [ColoredPoint]
    +depthPoints = do
    +  x <- [-width..width]
    +  y <- [0..height]
    +  let 
    +    neighbors = [(x,y),(x+1,y),(x+1,y+1),(x,y+1)]
    +    depthOf (u,v) = maxZeroIndex (ymandel u v) 0 deep 7
    +    -- zs are 3D points with found depth
    +    zs = map (\(u,v) -> (u,v,depthOf (u,v))) neighbors
    +    -- ts are 3D pixels + mandel value
    +    ts = map (\(u,v,w) -> (u,v,w,ymandel u v (w+1))) zs
    +    -- ps are 3D opengl points + color value
    +    ps = map (\(u,v,w,c') -> 
    +        (u/width,v/height,w/deep,colorFromValue c')) ts
    +  -- If the point diverged too fast, don't display it
    +  if (and $ map (\(_,_,_,c) -> c>=57) ts)
    +  then []
    +  -- Draw two triangles
    +  else [ps!!0,ps!!1,ps!!2,ps!!0,ps!!2,ps!!3]
    +
    +-- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex func minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if (func medpoint) /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    +colorFromValue n =
    +  let 
    +      t :: Int -> GLfloat
    +      t i = 0.7 + 0.3*cos( fromIntegral i / 10 )
    +  in
    +    ((t n),(t (n+5)),(t (n+10)))
    +
    +ymandel x y z = mandel (2*x/width) (2*y/height) (2*z/deep) 64
    +
    + +

    This code is cleaner but many things doesn’t feel right. First, all the user interaction code is outside our main file. I feel it is okay to hide the detail for the rendering. But I would have preferred to control the user actions.

    +

    On the other hand, we continue to handle a lot rendering details. For example, we provide ordered vertices.

    +

    Download the source code of this section → 04_Mandelbulb/Mandelbulb.lhs

    +
    +

    Download the source code of this section → 05_Mandelbulb/Mandelbulb.lhs

    +

    Functional organization?

    +

    Some points:

    +
      +
    1. OpenGL and GLUT is done in C. In particular the mainLoop function is a direct link to the C library (FFI). This function is clearly far from the functional paradigm. Could we make this better? We will have two choices:
    2. +
    +
      +
    • create our own mainLoop function to make it more functional.
    • +
    • deal with the imperative nature of the GLUT mainLoop function.
    • +
    +

    As one of the goal of this article is to understand how to deal with existing libraries and particularly the one coming from imperative languages, we will continue to use the mainLoop function. 2. Our main problem come from user interaction. If you ask “the Internet”, about how to deal with user interaction with a functional paradigm, the main answer is to use functional reactive programming (FRP). I won’t use FRP in this article. Instead, I’ll use a simpler while less effective way to deal with user interaction. But The method I’ll use will be as pure and functional as possible.

    +

    Here is how I imagine things should go. First, what the main loop should look like if we could make our own:

    +
    functionalMainLoop =
    +    Read user inputs and provide a list of actions
    +    Apply all actions to the World
    +    Display one frame 
    +    repetere aeternum
    +

    Clearly, ideally we should provide only three parameters to this main loop function:

    +
      +
    • an initial World state
    • +
    • a mapping between the user interactions and functions which modify the world
    • +
    • a function taking two parameters: time and world state and render a new world without user interaction.
    • +
    +

    Here is a real working code, I’ve hidden most display functions. The YGL, is a kind of framework to display 3D functions. But it can easily be extended to many kind of representation.

    +
    +
    import YGL -- Most the OpenGL Boilerplate
    +import Mandel -- The 3D Mandelbrot maths
    +
    + +

    We first set the mapping between user input and actions. The type of each couple should be of the form (user input, f) where (in a first time) f:World -> World. It means, the user input will transform the world state.

    +
    +
    -- Centralize all user input interaction
    +inputActionMap :: InputMap World
    +inputActionMap = inputMapFromList [
    +     (Press 'k' , rotate xdir   5)
    +    ,(Press 'i' , rotate xdir (-5))
    +    ,(Press 'j' , rotate ydir   5)
    +    ,(Press 'l' , rotate ydir (-5))
    +    ,(Press 'o' , rotate zdir   5)
    +    ,(Press 'u' , rotate zdir (-5))
    +    ,(Press 'f' , translate xdir   0.1)
    +    ,(Press 's' , translate xdir (-0.1))
    +    ,(Press 'e' , translate ydir   0.1)
    +    ,(Press 'd' , translate ydir (-0.1))
    +    ,(Press 'z' , translate zdir   0.1)
    +    ,(Press 'r' , translate zdir (-0.1))
    +    ,(Press '+' , zoom    1.1)
    +    ,(Press '-' , zoom (1/1.1))
    +    ,(Press 'h' , resize    1.2)
    +    ,(Press 'g' , resize (1/1.2))
    +    ]
    +
    + +

    And of course a type design the World State. The important part is that it is our World State type. We could have used any kind of data type.

    +
    +
    -- I prefer to set my own name for these types
    +data World = World {
    +      angle       :: Point3D
    +    , scale       :: Scalar
    +    , position    :: Point3D
    +    , shape       :: Scalar -> Function3D
    +    , box         :: Box3D
    +    , told        :: Time -- last frame time
    +    } 
    +
    + +

    The important part to glue our own type to the framework is to make our type an instance of the type class DisplayableWorld. We simply have to provide the definition of some functions.

    +
    +
    instance DisplayableWorld World where
    +  winTitle _ = "The YGL Mandelbulb"
    +  camera w = Camera {
    +        camPos = position w, 
    +        camDir = angle w,
    +        camZoom = scale w }
    +  -- objects for world w
    +  -- is the list of one unique element
    +  -- The element is an YObject
    +  --   more precisely the XYFunc Function3D Box3D
    +  --   where the Function3D is the type
    +  --             Point -> Point -> Maybe (Point,Color)
    +  --   and its value here is ((shape w) res)
    +  --   and the Box3D value is defbox
    +  objects w = [XYFunc ((shape  w) res) defbox]
    +              where
    +                  res = resolution $ box w
    +                  defbox = box w
    +
    + +

    The camera function will retrieve an object of type Camera which contains most necessary information to set our camera. The objects function will returns a list of objects. Their type is YObject. Note the generation of triangles is no more in this file. Until here we only used declarative pattern.

    +

    We also need to set all our transformation functions. These function are used to update the world state.

    +
    +
    xdir :: Point3D
    +xdir = makePoint3D (1,0,0)
    +ydir :: Point3D
    +ydir = makePoint3D (0,1,0)
    +zdir :: Point3D
    +zdir = makePoint3D (0,0,1)
    +
    + +

    Note (-*<) is the scalar product (α -*< (x,y,z) = (αx,αy,αz)). Also note we could add two Point3D.

    +
    +
    rotate :: Point3D -> Scalar -> World -> World
    +rotate dir angleValue world = 
    +  world {
    +     angle = (angle world) + (angleValue -*< dir) }
    +
    +translate :: Point3D -> Scalar -> World -> World
    +translate dir len world = 
    +  world {
    +    position = (position world) + (len -*< dir) }
    +
    +zoom :: Scalar -> World -> World
    +zoom z world = world {
    +    scale = z * scale world }
    +
    +resize :: Scalar -> World -> World
    +resize r world = world {
    +    box = (box world) {
    +     resolution = sqrt ((resolution (box world))**2 * r) }}
    +
    + +

    The resize is used to generate the 3D function. As I wanted the time spent to generate a more detailed view to grow linearly I use this not so straightforward formula.

    +

    The yMainLoop takes three arguments.

    +
      +
    • A map between user Input and world transformation
    • +
    • A timed world transformation
    • +
    • An initial world state
    • +
    +
    +
    main :: IO ()
    +main = yMainLoop inputActionMap idleAction initialWorld
    +
    + +

    Here is our initial world state.

    +
    +
    -- We initialize the world state
    +-- then angle, position and zoom of the camera
    +-- And the shape function
    +initialWorld :: World
    +initialWorld = World {
    +   angle = makePoint3D (-30,-30,0)
    + , position = makePoint3D (0,0,0)
    + , scale = 0.8
    + , shape = shapeFunc 
    + , box = Box3D { minPoint = makePoint3D (-2,-2,-2)
    +               , maxPoint =  makePoint3D (2,2,2)
    +               , resolution =  0.16 }
    + , told = 0
    + }
    +
    + +

    We will define shapeFunc later. Here is the function which transform the world even without user action. Mainly it makes some rotation.

    +
    +
    idleAction :: Time -> World -> World
    +idleAction tnew world = world {
    +    angle = (angle world) + (delta -*< zdir)
    +  , told = tnew
    +  }
    +  where 
    +      anglePerSec = 5.0
    +      delta = anglePerSec * elapsed / 1000.0
    +      elapsed = fromIntegral (tnew - (told world))
    +
    + +

    Now the function which will generate points in 3D. The first parameter (res) is the resolution of the vertex generation. More precisely, res is distance between two points on one direction. We need it to “close” our shape.

    +

    The type Function3D is Point -> Point -> Maybe Point. Because we consider partial functions (for some (x,y) our function can be undefined).

    +
    +
    shapeFunc :: Scalar -> Function3D
    +shapeFunc res x y = 
    +  let 
    +      z = maxZeroIndex (ymandel x y) 0 1 20
    +  in
    +  if and [ maxZeroIndex (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 |
    +              val <- [res], xeps <- [-val,val], yeps<-[-val,val]]
    +      then Nothing 
    +      else Just (z,colorFromValue ((ymandel x y z) * 64))
    +
    + +

    With the color function.

    +
    +
    colorFromValue :: Point -> Color
    +colorFromValue n =
    +  let 
    +      t :: Point -> Scalar
    +      t i = 0.7 + 0.3*cos( i / 10 )
    +  in
    +    makeColor (t n) (t (n+5)) (t (n+10))
    +
    + +

    The rest is similar to the preceding sections.

    +
    +
    -- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => 
    +                 (a -> b) -> a -> a -> Int -> a
    +maxZeroIndex _ minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if (func medpoint) /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    +ymandel :: Point -> Point -> Point -> Point
    +ymandel x y z = fromIntegral (mandel x y z 64) / 64
    +
    + +

    I won’t explain how the magic occurs here. If you are interested, just read the file YGL.hs. It is commented a lot.

    + +

    Download the source code of this section → 05_Mandelbulb/Mandelbulb.lhs

    +
    +

    Download the source code of this section → 06_Mandelbulb/Mandelbulb.lhs

    +

    Optimization

    +

    Our code architecture feel very clean. All the meaningful code is in our main file and all display details are externalized. If you read the code of YGL.hs, you’ll see I didn’t made everything perfect. For example, I didn’t finished the code of the lights. But I believe it is a good first step and it will be easy to go further. Unfortunately the program of the preceding session is extremely slow. We compute the Mandelbulb for each frame now.

    +

    Before our program structure was:

    +
    Constant Function -> Constant List of Triangles -> Display
    +

    Now we have

    +
    Main loop -> World -> Function -> List of Objects -> Atoms -> Display
    +

    The World state could change. The compiler can no more optimize the computation for us. We have to manually explain when to redraw the shape.

    +

    To optimize we must do some things in a lower level. Mostly the program remains the same, but it will provide the list of atoms directly.

    +
    + +
    +
    import YGL -- Most the OpenGL Boilerplate
    +import Mandel -- The 3D Mandelbrot maths
    +
    +-- Centralize all user input interaction
    +inputActionMap :: InputMap World
    +inputActionMap = inputMapFromList [
    +     (Press ' ' , switchRotation)
    +    ,(Press 'k' , rotate xdir 5)
    +    ,(Press 'i' , rotate xdir (-5))
    +    ,(Press 'j' , rotate ydir 5)
    +    ,(Press 'l' , rotate ydir (-5))
    +    ,(Press 'o' , rotate zdir 5)
    +    ,(Press 'u' , rotate zdir (-5))
    +    ,(Press 'f' , translate xdir 0.1)
    +    ,(Press 's' , translate xdir (-0.1))
    +    ,(Press 'e' , translate ydir 0.1)
    +    ,(Press 'd' , translate ydir (-0.1))
    +    ,(Press 'z' , translate zdir 0.1)
    +    ,(Press 'r' , translate zdir (-0.1))
    +    ,(Press '+' , zoom 1.1)
    +    ,(Press '-' , zoom (1/1.1))
    +    ,(Press 'h' , resize 2.0)
    +    ,(Press 'g' , resize (1/2.0))
    +    ]
    +
    + +
    + +
    +
    data World = World {
    +      angle       :: Point3D
    +    , anglePerSec :: Scalar
    +    , scale       :: Scalar
    +    , position    :: Point3D
    +    , box         :: Box3D
    +    , told        :: Time 
    +    -- We replace shape by cache
    +    , cache       :: [YObject]
    +    } 
    +
    + +
    +
    instance DisplayableWorld World where
    +  winTitle _ = "The YGL Mandelbulb"
    +  camera w = Camera {
    +        camPos = position w, 
    +        camDir = angle w,
    +        camZoom = scale w }
    +  -- We update our objects instanciation
    +  objects = cache
    +
    + +
    + +
    +
    xdir :: Point3D
    +xdir = makePoint3D (1,0,0)
    +ydir :: Point3D
    +ydir = makePoint3D (0,1,0)
    +zdir :: Point3D
    +zdir = makePoint3D (0,0,1)
    +
    +rotate :: Point3D -> Scalar -> World -> World
    +rotate dir angleValue world = 
    +  world {
    +     angle = angle world + (angleValue -*< dir) }
    +
    +switchRotation :: World -> World
    +switchRotation world = 
    +  world {
    +     anglePerSec = if anglePerSec world > 0 then 0 else 5.0 }
    +
    +translate :: Point3D -> Scalar -> World -> World
    +translate dir len world = 
    +  world {
    +    position = position world + (len -*< dir) }
    +
    +zoom :: Scalar -> World -> World
    +zoom z world = world {
    +    scale = z * scale world }
    +
    + +
    +
    main :: IO ()
    +main = yMainLoop inputActionMap idleAction initialWorld
    +
    + +
    + +

    Our initial world state is slightly changed:

    +
    +
    -- We initialize the world state
    +-- then angle, position and zoom of the camera
    +-- And the shape function
    +initialWorld :: World
    +initialWorld = World {
    +   angle = makePoint3D (30,30,0)
    + , anglePerSec = 5.0
    + , position = makePoint3D (0,0,0)
    + , scale = 1.0
    + , box = Box3D { minPoint = makePoint3D (0-eps, 0-eps, 0-eps)
    +               , maxPoint = makePoint3D (0+eps, 0+eps, 0+eps)
    +               , resolution =  0.02 }
    + , told = 0
    + -- We declare cache directly this time
    + , cache = objectFunctionFromWorld initialWorld
    + }
    + where eps=2
    +
    + +

    The use of eps is a hint to make a better zoom by computing with the right bounds.

    +

    We use the YGL.getObject3DFromShapeFunction function directly. This way instead of providing XYFunc, we provide directly a list of Atoms.

    +
    +
    objectFunctionFromWorld :: World -> [YObject]
    +objectFunctionFromWorld w = [Atoms atomList]
    +  where atomListPositive = 
    +          getObject3DFromShapeFunction
    +              (shapeFunc (resolution (box w))) (box w)
    +        atomList = atomListPositive ++ 
    +          map negativeTriangle atomListPositive
    +        negativeTriangle (ColoredTriangle (p1,p2,p3,c)) = 
    +              ColoredTriangle (negz p1,negz p3,negz p2,c)
    +              where negz (P (x,y,z)) = P (x,y,-z)
    +
    + +

    We know that resize is the only world change that necessitate to recompute the list of atoms (triangles). Then we update our world state accordingly.

    +
    +
    resize :: Scalar -> World -> World
    +resize r world = 
    +  tmpWorld { cache = objectFunctionFromWorld tmpWorld }
    +  where 
    +      tmpWorld = world { box = (box world) {
    +              resolution = sqrt ((resolution (box world))**2 * r) }}
    +
    + +

    All the rest is exactly the same.

    +
    + +
    +
    idleAction :: Time -> World -> World
    +idleAction tnew world = 
    +      world {
    +        angle = angle world + (delta -*< zdir)
    +      , told = tnew
    +      }
    +  where 
    +      delta = anglePerSec world * elapsed / 1000.0
    +      elapsed = fromIntegral (tnew - (told world))
    +
    +shapeFunc :: Scalar -> Function3D
    +shapeFunc res x y = 
    +  let 
    +      z = maxZeroIndex (ymandel x y) 0 1 20
    +  in
    +  if and [ maxZeroIndex (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 |
    +              val <- [res], xeps <- [-val,val], yeps<-[-val,val]]
    +      then Nothing 
    +      else Just (z,colorFromValue 0)
    +
    +colorFromValue :: Point -> Color
    +colorFromValue n =
    +  let 
    +      t :: Point -> Scalar
    +      t i = 0.0 + 0.5*cos( i /10 )
    +  in
    +    makeColor (t n) (t (n+5)) (t (n+10))
    +
    +-- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => 
    +                 (a -> b) -> a -> a -> Int -> a
    +maxZeroIndex _ minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if func medpoint /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    +ymandel :: Point -> Point -> Point -> Point
    +ymandel x y z = fromIntegral (mandel x y z 64) / 64
    +
    + +
    + +

    And you can also consider minor changes in the YGL.hs source file.

    + +

    Download the source code of this section → 06_Mandelbulb/Mandelbulb.lhs

    +

    Conclusion

    +

    As we can use imperative style in a functional language, know you can use functional style in imperative languages. This article exposed a way to organize some code in a functional way. I’d like to stress the usage of Haskell made it very simple to achieve this.

    +

    Once you are used to pure functional style, it is hard not to see all advantages it offers.

    +

    The code in the two last sections is completely pure and functional. Furthermore I don’t use GLfloat, Color3 or any other OpenGL type. If I want to use another library in the future, I would be able to keep all the pure code and simply update the YGL module.

    +

    The YGL module can be seen as a “wrapper” around 3D display and user interaction. It is a clean separator between the imperative paradigm and functional paradigm.

    +

    If you want to go further, it shouldn’t be hard to add parallelism. This should be easy mainly because most of the visible code is pure. Such an optimization would have been harder by using directly the OpenGL library.

    +

    You should also want to make a more precise object. Because, the Mandelbulb is clearly not convex. But a precise rendering might be very long from O(n².log(n)) to O(n³).

    +
    +
    +
      +
    1. Unfortunately, I couldn’t make this program to work on my Mac. More precisely, I couldn’t make the DevIL library work on Mac to output the image. Yes I have done a brew install libdevil. But even a minimal program who simply write some jpg didn’t worked. I tried both with Haskell and C.

    2. +
    3. Generally in Haskell you need to declare a lot of import lines. This is something I find annoying. In particular, it should be possible to create a special file, Import.hs which make all the necessary import for you, as you generally need them all. I understand why this is cleaner to force the programmer not to do so, but, each time I do a copy/paste, I feel something is wrong. I believe this concern can be generalized to the lack of namespace in Haskell.

    4. +
    5. I tried Complex Double, Complex Float, this current data type with Double and the actual version Float. For rendering a 1024x1024 Mandelbrot set it takes Complex Double about 6.8s, for Complex Float about 5.1s, for the actual version with Double and Float it takes about 1.6 sec. See these sources for testing yourself: https://gist.github.com/2945043. If you really want to things to go faster, use data Complex = C {-# UNPACK #-} !Float {-# UNPACK #-} !Float. It takes only one second instead of 1.6s.

    6. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2012-06-15 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/00_preamble.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/00_preamble.lhs new file mode 100644 index 0000000..d07230d --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/00_preamble.lhs @@ -0,0 +1,124 @@ +begindiv(intro) + +en: I really believe all developer should learn Haskell. +en: I don't think all should be super Haskell ninjas, +en: but at least, they should discover what Haskell has to offer. +en: Learning Haskell open your mind. +fr: Je pense vraiment que +fr: tous les développeurs devraient apprendre Haskell. +fr: Peut-être pas devenir des ninjas d'Haskell, +fr: mais au moins savoir ce que ce langage a de particulier. +fr: Son apprentissage ouvre énormément l'esprit. + +en: Mainstream languages share the same foundations: +fr: La plupart des langages partagent les mêmes fondamentaux : + +en: - variables +en: - loops +en: - pointers[^0001] +en: - data structures, objects and classes (for most) +fr: - les variables +fr: - les boucles +fr: - les pointeurs[^0001] +fr: - les structures de données, les objets et les classes + +en: [^0001]: Even if most recent languages try to hide them, they are present. +fr: [^0001]: Même si tous les langages récents essayent de les cacher, ils restent présents. + +en: Haskell is very different. +en: This language uses a lot of concepts I had never heard about before. +en: Many of those concepts will help you become a better programmer. +fr: Haskell est très différent. +fr: Ce langage utilise des concepts dont je n'avais jamais entendu parlé avant. +fr: Beaucoup de ces concepts pourront vous aider à devenir un meilleur développeur. + +en: But, learning Haskell can be hard. +en: It was for me. +en: In this article I try to provide what I lacked during my learning. +fr: Plier son esprit à Haskell peut être difficile. +fr: Ce le fût pour moi. +fr: Dans cet article, j'essaye de fournir les informations qui m'ont manquées lors de mon apprentissage. + +en: This article will certainly be hard to follow. +en: This is on purpose. +en: There is no shortcut to learning Haskell. +en: It is hard and challenging. +en: But I believe this is a good thing. +en: It is because it is hard that Haskell is interesting. +fr: Cet article sera certainement difficile à suivre. +fr: Mais c'est voulu. +fr: Il n'y a pas de raccourci pour apprendre Haskell. +fr: C'est difficile. +fr: Mais je pense que c'est une bonne chose. +fr: C'est parce qu'Haskell est difficile qu'il est intéressant. + +en: The conventional method to learning Haskell is to read two books. +en: First ["Learn You a Haskell"](http://learnyouahaskell.com) and just after ["Real World Haskell"](http://www.realworldhaskell.org). +en: I also believe this is the right way to go. +en: But, to learn what Haskell is all about, you'll have to read them in detail. +fr: La manière conventionnelle d'apprendre Haskell est de lire deux livres. +fr: En premier ["Learn You a Haskell"](http://learnyouahaskell.com) +fr: et ensuite ["Real World Haskell"](http://www.realworldhaskell.org). +fr: Je pense aussi que c'est la bonne manière de s'y prendre. +fr: Mais apprendre même un tout petit peu d'Haskell est presque impossible sans se plonger réellement dans ces livres. + +en: On the other hand, this article is a very brief and dense overview of all major aspects of Haskell. +en: I also added some informations I lacked while I learned Haskell. +fr: Cet article fait un résumé très dense et rapide des aspect majeurs d'Haskell. +fr: J'y ai aussi rajouté des informations qui m'ont manqué pendant l'apprentissage de ce langage. + +fr: Pour les francophones ; je suis désolé. +fr: Je n'ai pas eu le courage de tout retraduire en français. +fr: Sachez cependant que si vous êtes plusieurs à insister, je ferai certainement l'effort de traduire l'article en entier. +fr: Et si vous vous sentez d'avoir une bonne âme je ne suis pas contre un peu d'aide. +fr: Les sources de cet article sont sur [gihub](http://github.com/yogsototh/learn_haskell.git). + +en: The article contains five parts: +fr: Cet article contient cinq parties : + +en: - Introduction: a short example to show Haskell can be friendly. +en: - Basic Haskell: Haskell syntax, and some essential notions. +en: - Hard Difficulty Part: +en: - Functional style; a progressive example, from imperative to functional style +en: - Types; types and a standard binary tree example +en: - Infinite Structure; manipulate an infinite binary tree! +en: - Hell Difficulty Part: +en: - Deal with IO; A very minimal example +en: - IO trick explained; the hidden detail I lacked to understand IO +en: - Monads; incredible how we can generalize +en: - Appendix: +en: - More on infinite tree; a more math oriented discussion about infinite trees + +fr: - Introduction : un exemple rapide pour montrer qu'Haskell peut être facile. +fr: - Les bases d'Haskell : La syntaxe et des notions essentielles +fr: - Partie difficile : +fr: - Style fonctionnel : un exemple progressif, du style impératif au style fonctionnel ; +fr: - Types : la syntaxe et un exemple d'arbre binaire ; +fr: - Structure infinie : manipulons un arbre infini ! +fr: - Partie de difficulté infernale : +fr: - Utiliser les IO : un exemple très minimal ; +fr: - Le truc des IO révélé : les détails cachés d'IO qui m'ont manqués +fr: - Les monades : incroyable à quel point on peut généraliser +fr: - Appendice : +fr: - Revenons sur les arbres infinis : une discussion plus mathématique sur la manipulation d'arbres infinis. + +en: > Note: Each time you'll see a separator with a filename ending in `.lhs`, +en: > you could click the filename to get this file. +en: > If you save the file as `filename.lhs`, you can run it with +en: >
    +en:  > runhaskell filename.lhs
    +en:  > 
    +en: > +en: > Some might not work, but most will. +en: > You should see a link just below. + +fr: > Note: Chaque fois que vous voyez un séparateur avec un nom de fichier se terminant par `lhs`, vous pouvez cliquer sur le nom de fichier et télécharger le fichier. +fr: > Si vous sauvegardez le fichier sour le nom `filename.lhs`, vous pouvez l'exécuter avec : +fr: >
    +fr:  > runhaskell filename.lhs
    +fr:  > 
    +fr: > +fr: > Certain ne marcheront pas, mais la majorité vous donneront un résultat. +fr: > Vous devriez voir un lien juste en dessous. + +enddiv diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/00_hello_world.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/00_hello_world.lhs new file mode 100644 index 0000000..1acb772 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/00_hello_world.lhs @@ -0,0 +1,41 @@ +

    Introduction

    + +

    Install

    + +<%= blogimage("Haskell-logo.png", "Haskell logo") %> + +- [Haskell Platform](http://www.haskell.org/platform) is the standard way to install Haskell. + +Tools: + +- `ghc`: Compiler similar to gcc for `C`. +- `ghci`: Interactive Haskell (REPL) +- `runhaskell`: Execute a program without compiling it. Convenient but very slow compared to compiled programs. + +

    Don't be afraid

    + +<%= blogimage("munch_TheScream.jpg","The Scream") %> + +Many book/articles about Haskell start by introducing some esoteric formula (quick sort, Fibonacci, etc...). +I will do the exact opposite. +At first I won't show you any Haskell super power. +I will start with similarities between Haskell and other programming languages. +Let's jump to the mandatory "Hello World". + +> main = putStrLn "Hello World!" + +To run it, you can save this code in a `hello.hs` and: + + +~ runhaskell ./hello.hs +Hello World! + + +You could also download the literate Haskell source. +You should see a link just above the introduction title. +Download this file as `00_hello_world.lhs` and: + + +~ runhaskell 00_hello_world.lhs +Hello World! + diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/10_hello_you.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/10_hello_you.lhs new file mode 100644 index 0000000..4019a9e --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/10_hello_you.lhs @@ -0,0 +1,44 @@ +Now, a program asking your name and replying "Hello" using the name you entered: + +> main = do +> print "What is your name?" +> name <- getLine +> print ("Hello " ++ name ++ "!") + +First, let us compare with a similar program in some imperative languages: + + + # Python +print "What is your name?" +name = raw_input() +print "Hello %s!" % name + + + + # Ruby +puts "What is your name?" +name = gets.chomp +puts "Hello #{name}!" + + + +// In C + #include +int main (int argc, char **argv) { + char name[666]; // <- An Evil Number! + // What if my name is more than 665 character long? + printf("What is your name?\n"); + scanf("%s", name); + printf("Hello %s!\n", name); + return 0; +} + + +The structure is the same, but there are some syntax differences. +A major part of this tutorial will be dedicated to explaining why. + +In Haskell, there is a `main` function and every object has a type. +The type of `main` is `IO ()`. +This means, `main` will cause side effects. + +Just remember that Haskell can look a lot like mainstream imperative languages. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/20_very_basic.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/20_very_basic.lhs new file mode 100644 index 0000000..e9987e5 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/20_very_basic.lhs @@ -0,0 +1,113 @@ +

    Very basic Haskell

    + +<%= blogimage("picasso_owl.jpg","Picasso minimal owl") %> + +Before continuing you need to be warned about some essential properties of Haskell. + +_Functional_ + +Haskell is a functional language. +If you have an imperative language background, you'll have to learn a lot of new things. +Hopefully many of these new concepts will help you to program even in imperative languages. + +_Smart Static Typing_ + +Instead of being in your way like in `C`, `C++` or `Java`, the type system is here to help you. + +_Purity_ + +Generally your functions won't modify anything in the outside world. +This means, it can't modify the value of a variable, can't get user input, can't write on the screen, can't launch a missile. +On the other hand, parallelism will be very easy to achieve. +Haskell makes it clear where effects occur and where you are pure. +Also, it will be far easier to reason about your program. +Most bugs will be prevented in the pure parts of your program. + +Furthermore pure functions follow a fundamental law in Haskell: + + > Applying a function with the same parameters always returns the same value. + +_Laziness_ + +Laziness by default is a very uncommon language design. +By default, Haskell evaluates something only when it is needed. +In consequence, it provides a very elegant way to manipulate infinite structures for example. + +A last warning on how you should read Haskell code. +For me, it is like reading scientific papers. +Some parts are very clear, but when you see a formula, just focus and read slower. +Also, while learning Haskell, it _really_ doesn't matter much if you don't understand syntax details. +If you meet a `>>=`, `<$>`, `<-` or any other weird symbol, just ignore them and follows the flow of the code. + +

    Function declaration

    + +You might be used to declare functions like this: + +In `C`: + + +int f(int x, int y) { + return x*x + y*y; +} + + +In Javascript: + + +function f(x,y) { + return x*x + y*y; +} + + +in Python: + + +def f(x,y): + return x*x + y*y + + +in Ruby: + + +def f(x,y) + x*x + y*y +end + + +In Scheme: + + +(define (f x y) + (+ (* x x) (* y y))) + + +Finally, the Haskell way is: + + +f x y = x*x + y*y + + +Very clean. No parenthesis, no `def`. + +Don't forget, Haskell uses functions and types a lot. +It is thus very easy to define them. +The syntax was particularly well thought for these objects. + +

    A Type Example

    + +The usual way is to declare the type of your function. +This is not mandatory. +The compiler is smart enough to discover it for you. + +Let's play a little. + +> -- We declare the type using :: +> f :: Int -> Int -> Int +> f x y = x*x + y*y +> +> main = print (f 2 3) + +~~~ +~ runhaskell 20_very_basic.lhs +13 +~~~ diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/21_very_basic.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/21_very_basic.lhs new file mode 100644 index 0000000..85be6fc --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/21_very_basic.lhs @@ -0,0 +1,20 @@ +Now try + +> f :: Int -> Int -> Int +> f x y = x*x + y*y +> +> main = print (f 2.3 4.2) + +You get this error: + +~~~ +21_very_basic.lhs:6:23: + No instance for (Fractional Int) + arising from the literal `4.2' + Possible fix: add an instance declaration for (Fractional Int) + In the second argument of `f', namely `4.2' + In the first argument of `print', namely `(f 2.3 4.2)' + In the expression: print (f 2.3 4.2) +~~~ + +The problem: `4.2` isn't an Int. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/22_very_basic.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/22_very_basic.lhs new file mode 100644 index 0000000..49bc8aa --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/22_very_basic.lhs @@ -0,0 +1,100 @@ +The solution, +don't declare the type for `f`. +Haskell will infer the most general type for us: + +> f x y = x*x + y*y +> +> main = print (f 2.3 4.2) + +It works! +Great, we don't have to declare a new function for every single type. +For example, in `C`, you'll have to declare a function for `int`, for `float`, for `long`, for `double`, etc... + +But, what type should we declare? +To discover the type Haskell has found for us, just launch ghci: + +
    
    +% ghci
    +GHCi, version 7.0.4: http://www.haskell.org/ghc/  :? for help
    +Loading package ghc-prim ... linking ... done.
    +Loading package integer-gmp ... linking ... done.
    +Loading package base ... linking ... done.
    +Loading package ffi-1.0 ... linking ... done.
    +Prelude> let f x y = x*x + y*y
    +Prelude> :type f
    +f :: Num a => a -> a -> a
    +
    + +Uh? What is this strange type? + +~~~ +Num a => a -> a -> a +~~~ + +First, let's focus on the right part `a -> a -> a`. +To understand it, just look at a list of progressive examples: + +| The written type | Its meaning | +| `Int` | the type `Int` | +| `Int -> Int` | the type function from `Int` to `Int` | +| `Float -> Int` | the type function from `Float` to `Int` | +| `a -> Int` | the type function from any type to `Int` | +| `a -> a` | the type function from any type `a` to the same type `a` | +| `a -> a -> a` | the type function of two arguments of any type `a` to the same type `a` | + +In the type `a -> a -> a`, the letter `a` is a _type variable_. +It means `f` is a function with two arguments and both arguments and the result have the same type. +The type variable `a` could take many different type value. +For example `Int`, `Integer`, `Float`... + +So instead of having a forced type like in `C` with declaring the function for `int`, `long`, `float`, `double`, etc... +We declare only one function like in a dynamically typed language. + +Generally `a` can be any type. +For example a `String`, an `Int`, but also more complex types, like `Trees`, other functions, etc... +But here our type is prefixed with `Num a => `. + +`Num` is a _type class_. +A type class can be understood as a set of types. +`Num` contains only types which behave like numbers. +More precisely, `Num` is class containing types who implement a specific list of functions, and in particular `(+)` and `(*)`. + +Type classes are a very powerful language construct. +We can do some incredibly powerful stuff with this. +More on this later. + +Finally, `Num a => a -> a -> a` means: + +Let `a` be a type belonging to the `Num` type class. +This is a function from type `a` to (`a -> a`). + +Yes, strange. +In fact, in Haskell no function really has two arguments. +Instead all functions have only one argument. +But we will note that taking two arguments is equivalent to taking one argument and returning a function taking the second argument as parameter. + +More precisely `f 3 4` is equivalent to `(f 3) 4`. +Note `f 3` is a function: + +~~~ +f :: Num a :: a -> a -> a + +g :: Num a :: a -> a +g = f 3 + +g y ⇔ 3*3 + y*y +~~~ + +Another notation exists for functions. +The lambda notation allows us to create functions without assigning them a name. +We call them anonymous function. +We could have written: + +~~~ +g = \y -> 3*3 + y*y +~~~ + +The `\` is used because it looks like `λ` and is ASCII. + +If you are not used to functional programming your brain should start to heat up. +It is time to make a real application. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/23_very_basic.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/23_very_basic.lhs new file mode 100644 index 0000000..3234eec --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/23_very_basic.lhs @@ -0,0 +1,10 @@ +But just before that, we should verify the type system works as expected: + +> f :: Num a => a -> a -> a +> f x y = x*x + y*y +> +> main = print (f 3 2.4) + +It works, because, `3` is a valid representation both for Fractional numbers like Float and for Integer. +As `2.4` is a Fractional number, `3` is then interpreted as being also a Fractional number. + diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/24_very_basic.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/24_very_basic.lhs new file mode 100644 index 0000000..0a58325 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/24_very_basic.lhs @@ -0,0 +1,17 @@ +If we force our function to work with different types, it will fail: + +> f :: Num a => a -> a -> a +> f x y = x*x + y*y +> +> x :: Int +> x = 3 +> y :: Float +> y = 2.4 +> main = print (f x y) -- won't work because type x ≠ type y + +The compiler complains. +The two parameters must have the same type. + +If you believe it is a bad idea, and the compiler should make the transformation +from a type to another for you, you should really watch this great (and funny) video: +[WAT](https://www.destroyallsoftware.com/talks/wat) diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/hello_you.c b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/hello_you.c new file mode 100644 index 0000000..257f3bd --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/hello_you.c @@ -0,0 +1,9 @@ +#include +int main (int argc, char **argv) { + char name[666]; // <- An Evil Number! + // What if my name is more than 665 character long? + printf("What is your name?\n"); + scanf("%s", name); + printf("Hello %s!\n", name); + return 0; +} diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/hello_you.py b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/hello_you.py new file mode 100644 index 0000000..65e386f --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/hello_you.py @@ -0,0 +1,3 @@ +print "What is your name?" +name = raw_input() +print "Hello %s!" % name diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/hello_you.rb b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/hello_you.rb new file mode 100644 index 0000000..78d391d --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/hello_you.rb @@ -0,0 +1,3 @@ +puts "What is your name?" +name = gets.chomp +puts "Hello #{name}!" diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/20_Essential_Haskell/00_notations.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/20_Essential_Haskell/00_notations.lhs new file mode 100644 index 0000000..d9ec02b --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/20_Essential_Haskell/00_notations.lhs @@ -0,0 +1,126 @@ +

    Essential Haskell

    + +<%= blogimage("kandinsky_gugg.jpg","Kandinsky Gugg") %> + +I suggest you to skim this part. +Think of it like a reference. +Haskell has a lot of features. +Many informations are missing here. +Get back here if notation feels strange. + +I use the `⇔` symbol to state that two expression are equivalent. +It is a meta notation, `⇔` does not exists in Haskell. +I will also use `⇒` to show what is the return of an expression. + +

    Notations

    + +
    Arithmetic
    + +~~~ +3 + 2 * 6 / 3 ⇔ 3 + ((2*6)/3) +~~~ + +
    Logic
    + +~~~ +True || False ⇒ True +True && False ⇒ False +True == False ⇒ False +True /= False ⇒ True (/=) is the operator for different +~~~ + +
    Powers
    + +~~~ +x^n for n an integral (understand Int or Integer) +x**y for y any kind of number (Float for example) +~~~ + +`Integer` have no limit except the capacity of your machine: + +~~~ +4^103 +102844034832575377634685573909834406561420991602098741459288064 +~~~ + +Yeah! +And also rational numbers FTW! +But you need to import the module `Data.Ratio`: + +~~~ +$ ghci +.... +Prelude> :m Data.Ratio +Data.Ratio> (11 % 15) * (5 % 3) +11 % 9 +~~~ + +
    Lists
    + +~~~ +[] ⇔ empty list +[1,2,3] ⇔ List of integral +["foo","bar","baz"] ⇔ List of String +1:[2,3] ⇔ [1,2,3], (:) prepend one element +1:2:[] ⇔ [1,2] +[1,2] ++ [3,4] ⇔ [1,2,3,4], (++) concatenate +[1,2,3] ++ ["foo"] ⇔ ERROR String ≠ Integral +[1..4] ⇔ [1,2,3,4] +[1,3..10] ⇔ [1,3,5,7,9] +[2,3,5,7,11..100] ⇔ ERROR! I am not so smart! +[10,9..1] ⇔ [10,9,8,7,6,5,4,3,2,1] +~~~ + +
    Strings
    + +In Haskell strings are list of `Char`. + +~~~ +'a' :: Char +"a" :: [Char] +"" ⇔ [] +"ab" ⇔ ['a','b'] ⇔ 'a':"b" ⇔ 'a':['b'] ⇔ 'a':'b':[] +"abc" ⇔ "ab"++"c" +~~~ + + > _Remark_: + > In real code you shouldn't use list of char to represent text. + > You should mostly use `Data.Text` instead. + > If you want to represent stream of ASCII char, you should use `Data.ByteString`. + +
    Tuples
    + +The type of couple is `(a,b)`. +Elements in a tuple can have different type. + +~~~ +-- All these tuple are valid +(2,"foo") +(3,'a',[2,3]) +((2,"a"),"c",3) + +fst (x,y) ⇒ x +snd (x,y) ⇒ y + +fst (x,y,z) ⇒ ERROR: fst :: (a,b) -> a +snd (x,y,z) ⇒ ERROR: snd :: (a,b) -> b +~~~ + +
    Deal with parentheses
    + +To remove some parentheses you can use two functions: `($)` and `(.)`. + +~~~ +-- By default: +f g h x ⇔ (((f g) h) x) + +-- the $ replace parenthesis from the $ +-- to the end of the expression +f g $ h x ⇔ f g (h x) ⇔ (f g) (h x) +f $ g h x ⇔ f (g h x) ⇔ f ((g h) x) +f $ g $ h x ⇔ f (g (h x)) + +-- (.) the composition function +(f . g) x ⇔ f (g x) +(f . g . h) x ⇔ f (g (h x)) +~~~ diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/20_Essential_Haskell/10a_Functions.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/20_Essential_Haskell/10a_Functions.lhs new file mode 100644 index 0000000..07a653c --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/20_Essential_Haskell/10a_Functions.lhs @@ -0,0 +1,73 @@ +

    Useful notations for functions

    + +Just a reminder: + +~~~ +x :: Int ⇔ x is of type Int +x :: a ⇔ x can be of any type +x :: Num a => a ⇔ x can be any type a + such that a belongs to Num type class +f :: a -> b ⇔ f is a function from a to b +f :: a -> b -> c ⇔ f is a function from a to (b→c) +f :: (a -> b) -> c ⇔ f is a function from (a→b) to c +~~~ + +Defining the type of a function before its declaration isn't mandatory. +Haskell infers the most general type for you. +But it is considered a good practice to do so. + +_Infix notation_ + +> square :: Num a => a -> a +> square x = x^2 + +Note `^` use infix notation. +For each infix operator there its associated prefix notation. +You just have to put it inside parenthesis. + +> square' x = (^) x 2 +> +> square'' x = (^2) x + +We can remove `x` in the left and right side! +It's called η-reduction. + +> square''' = (^2) + +Note we can declare function with `'` in their name. +Here: + + > `square` ⇔ `square'` ⇔ `square''` ⇔ `square '''` + +_Tests_ + +An implementation of the absolute function. + +> absolute :: (Ord a, Num a) => a -> a +> absolute x = if x >= 0 then x else -x + +Note: the `if .. then .. else` Haskell notation is more like the +`¤?¤:¤` C operator. You cannot forget the `else`. + +Another equivalent version: + +> absolute' x +> | x >= 0 = x +> | otherwise = -x + + > Notation warning: indentation is _important_ in Haskell. + > Like in Python, a bad indentation could break your code! + +
    + +> main = do +> print $ square 10 +> print $ square' 10 +> print $ square'' 10 +> print $ square''' 10 +> print $ absolute 10 +> print $ absolute (-10) +> print $ absolute' 10 +> print $ absolute' (-10) + +
    diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/10_Functions.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/10_Functions.lhs new file mode 100644 index 0000000..d867258 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/10_Functions.lhs @@ -0,0 +1,105 @@ +

    Hard Part

    + +The hard part can now begin. + +

    Functional style

    + +<%= blogimage("hr_giger_biomechanicallandscape_500.jpg","Biomechanical Landscape by H.R. Giger") %> + +In this section, I will give a short example of the impressive refactoring ability provided by Haskell. +We will select a problem and solve it using a standard imperative way. +Then I will make the code evolve. +The end result will be both more elegant and easier to adapt. + +Let's solve the following problem: + + > Given a list of integers, return the sum of the even numbers in the list. + > + > example: + > `[1,2,3,4,5] ⇒ 2 + 4 ⇒ 6` + +To show differences between the functional and imperative approach, +I'll start by providing an imperative solution (in Javascript): + + +function evenSum(list) { + var result = 0; + for (var i=0; i< list.length ; i++) { + if (list[i] % 2 ==0) { + result += list[i]; + } + } + return result; +} + + +But, in Haskell we don't have variables, nor for loop. +One solution to achieve the same result without loops is to use recursion. + + > _Remark_: + > Recursion is generally perceived as slow in imperative languages. + > But it is generally not the case in functional programming. + > Most of the time Haskell will handle recursive functions efficiently. + +Here is a `C` version of the recursive function. +Note that for simplicity, I assume the int list ends with the first `0` value. + + + +int evenSum(int *list) { + return accumSum(0,list); +} + +int accumSum(int n, int *list) { + int x; + int *xs; + if (*list == 0) { // if the list is empty + return n; + } else { + x = list[0]; // let x be the first element of the list + xs = list+1; // let xs be the list without x + if ( 0 == (x%2) ) { // if x is even + return accumSum(n+x, xs); + } else { + return accumSum(n, xs); + } + } +} + + +Keep this code in mind. We will translate it into Haskell. +But before, I need to introduce three simple but useful functions we will use: + + +even :: Integral a => a -> Bool +head :: [a] -> a +tail :: [a] -> [a] + + +`even` verifies if a number is even. + + +even :: Integral a => a -> Bool +even 3 ⇒ False +even 2 ⇒ True + + +`head` returns the first element of a list: + + +head :: [a] -> a +head [1,2,3] ⇒ 1 +head [] ⇒ ERROR + + +`tail` returns all elements of a list, except the first: + + +tail :: [a] -> [a] +tail [1,2,3] ⇒ [2,3] +tail [3] ⇒ [] +tail [] ⇒ ERROR + + +Note that for any non empty list `l`, +`l ⇔ (head l):(tail l)` diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/11_Functions.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/11_Functions.lhs new file mode 100644 index 0000000..712b96e --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/11_Functions.lhs @@ -0,0 +1,67 @@ +The first Haskell solution. +The function `evenSum` returns the sum of all even numbers in a list: + +> -- Version 1 +> evenSum :: [Integer] -> Integer +> +> evenSum l = accumSum 0 l +> +> accumSum n l = if l == [] +> then n +> else let x = head l +> xs = tail l +> in if even x +> then accumSum (n+x) xs +> else accumSum n xs + +To test a function you can use `ghci`: + +
    +% ghci
    +GHCi, version 7.0.3: http://www.haskell.org/ghc/  :? for help
    +Loading package ghc-prim ... linking ... done.
    +Loading package integer-gmp ... linking ... done.
    +Loading package base ... linking ... done.
    +Prelude> :load 11_Functions.lhs 
    +[1 of 1] Compiling Main             ( 11_Functions.lhs, interpreted )
    +Ok, modules loaded: Main.
    +*Main> evenSum [1..5]
    +6
    +
    + +Here is an example of execution[^2]: + +[^2]: I know I'm cheating. But I will talk about non-strict later. + +
    +*Main> evenSum [1..5]
    +accumSum 0 [1,2,3,4,5]
    +1 is odd
    +accumSum 0 [2,3,4,5]
    +2 is even
    +accumSum (0+2) [3,4,5]
    +3 is odd
    +accumSum (0+2) [4,5]
    +4 is even
    +accumSum (0+2+4) [5]
    +5 is odd
    +accumSum (0+2+4) []
    +l == []
    +0+2+4
    +0+6
    +6
    +
    + +Coming from an imperative language all should seem right. +In reality many things can be improved. +First, we can generalize the type. + + +evenSum :: Integral a => [a] -> a + + +
    + +> main = do print $ evenSum [1..10] + +
    diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/12_Functions.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/12_Functions.lhs new file mode 100644 index 0000000..9333919 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/12_Functions.lhs @@ -0,0 +1,21 @@ +Next, we can use sub functions using `where` or `let`. +This way our `accumSum` function won't pollute the global namespace. + +> -- Version 2 +> evenSum :: Integral a => [a] -> a +> +> evenSum l = accumSum 0 l +> where accumSum n l = +> if l == [] +> then n +> else let x = head l +> xs = tail l +> in if even x +> then accumSum (n+x) xs +> else accumSum n xs + +
    + +> main = print $ evenSum [1..10] + +
    diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/13_Functions.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/13_Functions.lhs new file mode 100644 index 0000000..57615ee --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/13_Functions.lhs @@ -0,0 +1,52 @@ +Next, we can use pattern matching. + +> -- Version 3 +> evenSum l = accumSum 0 l +> where +> accumSum n [] = n +> accumSum n (x:xs) = +> if even x +> then accumSum (n+x) xs +> else accumSum n xs + +What is pattern matching? +Use values instead of general parameter names[^021301]. + +[^021301]: For the brave, a more complete explanation of pattern matching can be found [here](http://www.cs.auckland.ac.nz/references/haskell/haskell-intro-html/patterns.html). + +Instead of saying: `foo l = if l == [] then else ` +You simply state: + + +foo [] = +foo l = + + +But pattern matching goes even further. +It is also able to inspect the inner data of a complex value. +We can replace + + +foo l = let x = head l + xs = tail l + in if even x + then foo (n+x) xs + else foo n xs + + +with + + +foo (x:xs) = if even x + then foo (n+x) xs + else foo n xs + + +This is a very useful feature. +It makes our code both terser and easier to read. + +
    + +> main = print $ evenSum [1..10] + +
    diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/14_Functions.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/14_Functions.lhs new file mode 100644 index 0000000..256a24f --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/14_Functions.lhs @@ -0,0 +1,31 @@ +In Haskell you can simplify function definition by η-reducing them. +For example, instead of writing: + + +f x = (some expresion) x + + +you can simply write + + +f = some expression + + +We use this method to remove the `l`: + +> -- Version 4 +> evenSum :: Integral a => [a] -> a +> +> evenSum = accumSum 0 +> where +> accumSum n [] = n +> accumSum n (x:xs) = +> if even x +> then accumSum (n+x) xs +> else accumSum n xs + +
    + +> main = print $ evenSum [1..10] + +
    diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/15_Functions.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/15_Functions.lhs new file mode 100644 index 0000000..bddff8f --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/15_Functions.lhs @@ -0,0 +1,100 @@ +

    Higher Order Functions

    + +<%= blogimage("escher_polygon.png","Escher") %> + +To make things even better we should use higher order functions. +What are these beasts? +Higher order functions are functions taking functions as parameter. + +Here are some examples: + + +filter :: (a -> Bool) -> [a] -> [a] +map :: (a -> b) -> [a] -> [b] +foldl :: (a -> b -> a) -> a -> [b] -> a + + +Let's proceed by small steps. + + +-- Version 5 +evenSum l = mysum 0 (filter even l) + where + mysum n [] = n + mysum n (x:xs) = mysum (n+x) xs + + +where + + +filter even [1..10] ⇔ [2,4,6,8,10] + + +The function `filter` takes a function of type (`a -> Bool`) and a list of type `[a]`. It returns a list containing only elements for which the function returned `true`. + +Our next step is to use another way to simulate a loop. +We will use the `foldl` function to accumulate a value. +The function `foldl` captures a general coding pattern: + +
    +myfunc list = foo initialValue list
    +    foo accumulated []     = accumulated
    +    foo tmpValue    (x:xs) = foo (bar tmpValue x) xs
    +
    + +Which can be replaced by: + +
    +myfunc list = foldl bar initialValue list
    +
    + +If you really want to know how the magic works. +Here is the definition of `foldl`. + + +foldl f z [] = z +foldl f z (x:xs) = foldl f (f z x) xs + + + +foldl f z [x1,...xn] +⇔ f (... (f (f z x1) x2) ...) xn + + +But as Haskell is lazy, it doesn't evaluate `(f z x)` and pushes it to the stack. +This is why we generally use `foldl'` instead of `foldl`; +`foldl'` is a _strict_ version of `foldl`. +If you don't understand what lazy and strict means, +don't worry, just follow the code as if `foldl` and `foldl'` where identical. + +Now our new version of `evenSum` becomes: + + +-- Version 6 +-- foldl' isn't accessible by default +-- we need to import it from the module Data.List +import Data.List +evenSum l = foldl' mysum 0 (filter even l) + where mysum acc value = acc + value + + +Version we can simplify by using directly a lambda notation. +This way we don't have to create the temporary name `mysum`. + +> -- Version 7 +> -- Generally it is considered a good practice +> -- to import only the necessary function(s) +> import Data.List (foldl') +> evenSum l = foldl' (\x y -> x+y) 0 (filter even l) + +And of course, we note that + + +(\x y -> x+y) ⇔ (+) + + +
    + +> main = print $ evenSum [1..10] + +
    diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/16_Functions.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/16_Functions.lhs new file mode 100644 index 0000000..d69732d --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/16_Functions.lhs @@ -0,0 +1,114 @@ +Finally + + +-- Version 8 +import Data.List (foldl') +evenSum :: Integral a => [a] -> a +evenSum l = foldl' (+) 0 (filter even l) + + +`foldl'` isn't the easiest function to intuit. +If you are not used to it, you should study it a bit. + +To help you understand what's going on here, a step by step evaluation: + +
    +  evenSum [1,2,3,4]
    +⇒ foldl' (+) 0 (filter even [1,2,3,4])
    +⇒ foldl' (+) 0 [2,4]
    +⇒ foldl' (+) (0+2) [4] 
    +⇒ foldl' (+) 2 [4]
    +⇒ foldl' (+) (2+4) []
    +⇒ foldl' (+) 6 []
    +⇒ 6
    +
    + + +Another useful higher order function is `(.)`. +The `(.)` function corresponds to the mathematical composition. + + +(f . g . h) x ⇔ f ( g (h x)) + + +We can take advantage of this operator to η-reduce our function: + + +-- Version 9 +import Data.List (foldl') +evenSum :: Integral a => [a] -> a +evenSum = (foldl' (+) 0) . (filter even) + + +Also, we could rename some parts to make it clearer: + +> -- Version 10 +> import Data.List (foldl') +> sum' :: (Num a) => [a] -> a +> sum' = foldl' (+) 0 +> evenSum :: Integral a => [a] -> a +> evenSum = sum' . (filter even) +> + +It is time to discuss a bit. +What did we gain by using higher order functions? + +At first, you can say it is terseness. +But in fact, it has more to do with better thinking. +Suppose we want to modify slightly our function. +We want to get the sum of all even square of element of the list. + +~~~ +[1,2,3,4] ▷ [1,4,9,16] ▷ [4,16] ▷ 20 +~~~ + +Update the version 10 is extremely easy: + +> squareEvenSum = sum' . (filter even) . (map (^2)) +> squareEvenSum' = evenSum . (map (^2)) +> squareEvenSum'' = sum' . (map (^2)) . (filter even) + +We just had to add another "transformation function"[^0216]. + +[^0216]: You should remark `squareEvenSum''` is more efficient that the two other versions. The order of `(.)` is important. + +~~~ +map (^2) [1,2,3,4] ⇔ [1,4,9,16] +~~~ + +The `map` function simply apply a function to all element of a list. + +We didn't had to modify anything _inside_ the function definition. +It feels more modular. +But in addition you can think more mathematically about your function. +You can then use your function as any other one. +You can compose, map, fold, filter using your new function. + +To modify version 1 is left as an exercise to the reader ☺. + +If you believe we reached the end of generalization, then know you are very wrong. +For example, there is a way to not only use this function on lists but on any recursive type. +If you want to know how, I suggest you to read this quite fun article: [Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire by Meijer, Fokkinga and Paterson](http://eprints.eemcs.utwente.nl/7281/0 +1/db-utwente-40501F46.pdf). + +This example should show you how great pure functional programming is. +Unfortunately, using pure functional programming isn't well suited to all usages. +Or at least such a language hasn't been found yet. + +One of the great powers of Haskell is the ability to create DSLs +(Domain Specific Language) +making it easy to change the programming paradigm. + +In fact, Haskell is also great when you want to write imperative style programming. +Understanding this was really hard for me when learning Haskell. +A lot of effort has been done to explain to you how much functional approach is superior. +Then when you start the imperative style of Haskell, it is hard to understand why and how. + +But before talking about this Haskell super-power, we must talk about another +essential aspect of Haskell: _Types_. + +
    + +> main = print $ evenSum [1..10] + +
    diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/20_Types.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/20_Types.lhs new file mode 100644 index 0000000..7b5be80 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/20_Types.lhs @@ -0,0 +1,114 @@ +

    Types

    + +<%= blogimage("salvador-dali-the-madonna-of-port-lligat.jpg","Dali, the madonna of port Lligat") %> + + > <%=tldr%> + > + > - `type Name = AnotherType` is just an alias and the compiler doesn't do any difference between `Name` and `AnotherType`. + > - `data Name = NameConstructor AnotherType` make a difference. + > - `data` can construct structures which can be recursives. + > - `deriving` is magic and create functions for you. + +In Haskell, types are strong and static. + +Why is this important? It will help you _greatly_ to avoid mistakes. +In Haskell, most bugs are caught during the compilation of your program. +And the main reason is because of the type inference during compilation. +It will be easy to detect where you used the wrong parameter at the wrong place for example. + + +

    Type inference

    + +Static typing is generally essential to reach fast execution time. +But most statically typed languages are bad at generalizing concepts. +Haskell's saving grace is that it can _infer_ types. + +Here is a simple example. +The `square` function in Haskell: + + +square x = x * x + + +This function can `square` any Numeral type. +You can provide `square` with an `Int`, an `Integer`, a `Float` a `Fractional` and even `Complex`. Proof by example: + +~~~ +% ghci +GHCi, version 7.0.4: +... +Prelude> let square x = x*x +Prelude> square 2 +4 +Prelude> square 2.1 +4.41 +Prelude> -- load the Data.Complex module +Prelude> :m Data.Complex +Prelude Data.Complex> square (2 :+ 1) +3.0 :+ 4.0 +~~~ + +`x :+ y` is the notation for the complex (x + ib). + +Now compare with the amount of code necessary in C: + + +int int_square(int x) { return x*x; } + +float float_square(float x) {return x*x; } + +complex complex_square (complex z) { + complex tmp; + tmp.real = z.real * z.real - z.img * z.img; + tmp.img = 2 * z.img * z.real; +} + +complex x,y; +y = complex_square(x); + + +For each type, you need to write a new function. +The only way to work around this problem is to use some meta-programming trick. +For example using the pre-processor. +In C++ there is a better way, the C++ templates: + + +#include +#include +using namespace std; + +template +T square(T x) +{ + return x*x; +} + +int main() { + // int + int sqr_of_five = square(5); + cout << sqr_of_five << endl; + // double + cout << (double)square(5.3) << endl; + // complex + cout << square( complex(5,3) ) + << endl; + return 0; +} + + +C++ does a far better job than C. +For more complex function the syntax can be hard to follow: +look at +[this article](http://bartoszmilewski.com/2009/10/21/what-does-haskell-have-to-do-with-c/) +for example. + +In C++ you must declare that a function can work with different types. +In Haskell this is the opposite. +The function will be as general as possible by default. + +Type inference gives Haskell the feeling of freedom that dynamically +typed languages provide. +But unlike dynamically typed languages, most errors are caught before the execution. +Generally, in Haskell: + + > "if it compiles it certainly does what you intended" diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/21_Types.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/21_Types.lhs new file mode 100644 index 0000000..9bc4183 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/21_Types.lhs @@ -0,0 +1,16 @@ +

    Type construction

    + +You can construct your own types. +First you can use aliases or type synonyms. + +> type Name = String +> type Color = String +> +> showInfos :: Name -> Color -> String +> showInfos name color = "Name: " ++ name +> ++ ", Color: " ++ color +> name :: Name +> name = "Robin" +> color :: Color +> color = "Blue" +> main = putStrLn $ showInfos name color diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/22_Types.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/22_Types.lhs new file mode 100644 index 0000000..8cb44d8 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/22_Types.lhs @@ -0,0 +1,75 @@ + +But it doesn't protect you much. +Try to swap the two parameter of `showInfos` and run the program: + + + putStrLn $ showInfos color name + + +It will compile and execute. +In fact you can replace Name, Color and String everywhere. +The compiler will treat them as completely identical. + +Another method is to create your own types using the keyword `data`. + +> data Name = NameConstr String +> data Color = ColorConstr String +> +> showInfos :: Name -> Color -> String +> showInfos (NameConstr name) (ColorConstr color) = +> "Name: " ++ name ++ ", Color: " ++ color +> +> name = NameConstr "Robin" +> color = ColorConstr "Blue" +> main = putStrLn $ showInfos name color + +Now if you switch parameters of `showInfos`, the compiler complains! +A possible mistake you could never do again. +The only price is to be more verbose. + +Also remark constructor are functions: + + +NameConstr :: String -> Name +ColorConstr :: String -> Color + + +The syntax of `data` is mainly: + + +data TypeName = ConstructorName [types] + | ConstructorName2 [types] + | ... + + +Generally the usage is to use the same name for the +DataTypeName and DataTypeConstructor. + +Example: + + +data Complex = Num a => Complex a a + + +Also you can use the record syntax: + + +data DataTypeName = DataConstructor { + field1 :: [type of field1] + , field2 :: [type of field2] + ... + , fieldn :: [type of fieldn] } + + +And many accessors are made for you. +Furthermore you can use another order when setting values. + +Example: + + +data Complex = Num a => Complex { real :: a, img :: a} +c = Complex 1.0 2.0 +z = Complex { real = 3, img = 4 } +real c ⇒ 1.0 +img z ⇒ 4 + diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/23_Types.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/23_Types.lhs new file mode 100644 index 0000000..3f6b728 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/23_Types.lhs @@ -0,0 +1,41 @@ +

    Recursive type

    + +You already encountered a recursive type: lists. +You can re-create lists, but with a more verbose syntax: + + +data List a = Empty | Cons a (List a) + + + +If you really want to use an easier syntax you can use an infix name for constructors. + + +infixr 5 ::: +data List a = Nil | a ::: (List a) + + +The number after `infixr` is the priority. + +If you want to be able to print (`Show`), read (`Read`), test equality (`Eq`) and compare (`Ord`) your new data structure you can tell Haskell to derive the appropriate functions for you. + +> infixr 5 ::: +> data List a = Nil | a ::: (List a) +> deriving (Show,Read,Eq,Ord) + +When you add `deriving (Show)` to your data declaration, Haskell create a `show` function for you. +We'll see soon how you can use your own `show` function. + +> convertList [] = Nil +> convertList (x:xs) = x ::: convertList xs + +> main = do +> print (0 ::: 1 ::: Nil) +> print (convertList [0,1]) + +This prints: + +~~~ +0 ::: (1 ::: Nil) +0 ::: (1 ::: Nil) +~~~ diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/30_Trees.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/30_Trees.lhs new file mode 100644 index 0000000..5cd713c --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/30_Trees.lhs @@ -0,0 +1,37 @@ +

    Trees

    + +<%= blogimage("magritte-l-arbre.jpg","Magritte, l'Arbre") %> + +We'll just give another standard example: binary trees. + +> import Data.List +> +> data BinTree a = Empty +> | Node a (BinTree a) (BinTree a) +> deriving (Show) + +We will also create a function which turns a list into an ordered binary tree. + +> treeFromList :: (Ord a) => [a] -> BinTree a +> treeFromList [] = Empty +> treeFromList (x:xs) = Node x (treeFromList (filter ( (treeFromList (filter (>x) xs)) + +Look at how elegant this function is. +In plain English: + +- an empty list will be converted to an empty tree. +- a list `(x:xs)` will be converted to a tree where: + - The root is `x` + - Its left subtree is the tree created from members of the list `xs` which are strictly inferior to `x` and + - the right subtree is the tree created from members of the list `xs` which are strictly superior to `x`. + +> main = print $ treeFromList [7,2,4,8] + +You should obtain the following: + +~~~ +Node 7 (Node 2 Empty (Node 4 Empty Empty)) (Node 8 Empty Empty) +~~~ + +This is an informative but quite unpleasant representation of our tree. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/31_Trees.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/31_Trees.lhs new file mode 100644 index 0000000..7620043 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/31_Trees.lhs @@ -0,0 +1,196 @@ +Just for fun, let's code a better display for our trees. +I simply had fun making a nice function to display trees in a general way. +You can safely skip this part if you find it too difficult to follow. + +We have a few changes to make. +We remove the `deriving (Show)` from the declaration of our `BinTree` type. +And it might also be useful to make our BinTree an instance of (`Eq` and `Ord`). +We will be able to test equality and compare trees. + +> data BinTree a = Empty +> | Node a (BinTree a) (BinTree a) +> deriving (Eq,Ord) + +Without the `deriving (Show)`, Haskell doesn't create a `show` method for us. +We will create our own version of `show`. +To achieve this, we must declare that our newly created type `BinTree a` +is an instance of the type class `Show`. +The general syntax is: + + +instance Show (BinTree a) where + show t = ... -- You declare your function here + + +Here is my version of how to show a binary tree. +Don't worry about the apparent complexity. +I made a lot of improvements in order to display even stranger objects. + +> -- declare BinTree a to be an instance of Show +> instance (Show a) => Show (BinTree a) where +> -- will start by a '<' before the root +> -- and put a : a begining of line +> show t = "< " ++ replace '\n' "\n: " (treeshow "" t) +> where +> -- treeshow pref Tree +> -- shows a tree and starts each line with pref +> -- We don't display the Empty tree +> treeshow pref Empty = "" +> -- Leaf +> treeshow pref (Node x Empty Empty) = +> (pshow pref x) +> +> -- Right branch is empty +> treeshow pref (Node x left Empty) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "`--" " " left) +> +> -- Left branch is empty +> treeshow pref (Node x Empty right) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "`--" " " right) +> +> -- Tree with left and right children non empty +> treeshow pref (Node x left right) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "|--" "| " left) ++ "\n" ++ +> (showSon pref "`--" " " right) +> +> -- shows a tree using some prefixes to make it nice +> showSon pref before next t = +> pref ++ before ++ treeshow (pref ++ next) t +> +> -- pshow replaces "\n" by "\n"++pref +> pshow pref x = replace '\n' ("\n"++pref) (show x) +> +> -- replaces one char by another string +> replace c new string = +> concatMap (change c new) string +> where +> change c new x +> | x == c = new +> | otherwise = x:[] -- "x" + + +The `treeFromList` method remains identical. + +> treeFromList :: (Ord a) => [a] -> BinTree a +> treeFromList [] = Empty +> treeFromList (x:xs) = Node x (treeFromList (filter ( (treeFromList (filter (>x) xs)) + +And now, we can play: + +> main = do +> putStrLn "Int binary tree:" +> print $ treeFromList [7,2,4,8,1,3,6,21,12,23] + +~~~ +Int binary tree: +< 7 +: |--2 +: | |--1 +: | `--4 +: | |--3 +: | `--6 +: `--8 +: `--21 +: |--12 +: `--23 +~~~ + +Now it is far better! +The root is shown by starting the line with the `<` character. +And each following line starts with a `:`. +But we could also use another type. + +> putStrLn "\nString binary tree:" +> print $ treeFromList ["foo","bar","baz","gor","yog"] + +~~~ +String binary tree: +< "foo" +: |--"bar" +: | `--"baz" +: `--"gor" +: `--"yog" +~~~ + +As we can test equality and order trees, we can +make tree of trees! + +> putStrLn "\nBinary tree of Char binary trees:" +> print ( treeFromList +> (map treeFromList ["baz","zara","bar"])) + +~~~ +Binary tree of Char binary trees: +< < 'b' +: : |--'a' +: : `--'z' +: |--< 'b' +: | : |--'a' +: | : `--'r' +: `--< 'z' +: : `--'a' +: : `--'r' +~~~ + +This is why I chose to prefix each line of tree display by `:` (except for the root). + +<%= blogimage("yo_dawg_tree.jpg","Yo Dawg Tree") %> + +> putStrLn "\nTree of Binary trees of Char binary trees:" +> print $ (treeFromList . map (treeFromList . map treeFromList)) +> [ ["YO","DAWG"] +> , ["I","HEARD"] +> , ["I","HEARD"] +> , ["YOU","LIKE","TREES"] ] + +Which is equivalent to + + +print ( treeFromList ( + map treeFromList + [ map treeFromList ["YO","DAWG"] + , map treeFromList ["I","HEARD"] + , map treeFromList ["I","HEARD"] + , map treeFromList ["YOU","LIKE","TREES"] ])) + + +and gives: + +~~~ +Binary tree of Binary trees of Char binary trees: +< < < 'Y' +: : : `--'O' +: : `--< 'D' +: : : |--'A' +: : : `--'W' +: : : `--'G' +: |--< < 'I' +: | : `--< 'H' +: | : : |--'E' +: | : : | `--'A' +: | : : | `--'D' +: | : : `--'R' +: `--< < 'Y' +: : : `--'O' +: : : `--'U' +: : `--< 'L' +: : : `--'I' +: : : |--'E' +: : : `--'K' +: : `--< 'T' +: : : `--'R' +: : : |--'E' +: : : `--'S' +~~~ + +Notice how duplicate trees aren't inserted; +there is only one tree corresponding to `"I","HEARD"`. +We have this for (almost) free, because we have declared Tree to be an instance of `Eq`. + +See how awesome this structure is. +We can make trees containing not only integers, strings and chars, but also other trees. +And we can even make a tree containing a tree of trees! diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/40_Infinites_Structures.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/40_Infinites_Structures.lhs new file mode 100644 index 0000000..1f88599 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/40_Infinites_Structures.lhs @@ -0,0 +1,43 @@ +

    Infinite Structures

    + +<%= blogimage("escher_infinite_lizards.jpg","Escher") %> + +It is often stated that Haskell is _lazy_. + +In fact, if you are a bit pedantic, you should state that [Haskell is _non-strict_](http://www.haskell.org/haskellwiki/Lazy_vs._non-strict). +Laziness is just a common implementation for non-strict languages. + +Then what does not-strict means? From the Haskell wiki: + + > Reduction (the mathematical term for evaluation) proceeds from the outside in. + > + > so if you have `(a+(b*c))` then you first reduce `+` first, then you reduce the inner `(b*c)` + +For example in Haskell you can do: + +> -- numbers = [1,2,..] +> numbers :: [Integer] +> numbers = 0:map (1+) numbers +> +> take' n [] = [] +> take' 0 l = [] +> take' n (x:xs) = x:take' (n-1) xs +> +> main = print $ take' 10 numbers + +And it stops. + +How? + +Instead of trying to evaluate `numbers` entirely, +it evaluates elements only when needed. + +Also, note in Haskell there is a notation for infinite lists + +~~~ +[1..] ⇔ [1,2,3,4...] +[1,3..] ⇔ [1,3,5,7,9,11...] +~~~ + +And most functions will work with them. +Also, there is a built-in function `take` which is equivalent to our `take'`. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/41_Infinites_Structures.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/41_Infinites_Structures.lhs new file mode 100644 index 0000000..6dd60fd --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/41_Infinites_Structures.lhs @@ -0,0 +1,157 @@ + +
    + +This code is mostly the same as the previous one. + +> import Debug.Trace (trace) +> import Data.List +> data BinTree a = Empty +> | Node a (BinTree a) (BinTree a) +> deriving (Eq,Ord) + +> -- declare BinTree a to be an instance of Show +> instance (Show a) => Show (BinTree a) where +> -- will start by a '<' before the root +> -- and put a : a begining of line +> show t = "< " ++ replace '\n' "\n: " (treeshow "" t) +> where +> treeshow pref Empty = "" +> treeshow pref (Node x Empty Empty) = +> (pshow pref x) +> +> treeshow pref (Node x left Empty) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "`--" " " left) +> +> treeshow pref (Node x Empty right) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "`--" " " right) +> +> treeshow pref (Node x left right) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "|--" "| " left) ++ "\n" ++ +> (showSon pref "`--" " " right) +> +> -- show a tree using some prefixes to make it nice +> showSon pref before next t = +> pref ++ before ++ treeshow (pref ++ next) t +> +> -- pshow replace "\n" by "\n"++pref +> pshow pref x = replace '\n' ("\n"++pref) (" " ++ show x) +> +> -- replace on char by another string +> replace c new string = +> concatMap (change c new) string +> where +> change c new x +> | x == c = new +> | otherwise = x:[] -- "x" +> + +
    + +Suppose we don't mind having an ordered binary tree. +Here is an infinite binary tree: + +> nullTree = Node 0 nullTree nullTree + +A complete binary tree where each node is equal to 0. +Now I will prove you can manipulate this object using the following function: + +> -- take all element of a BinTree +> -- up to some depth +> treeTakeDepth _ Empty = Empty +> treeTakeDepth 0 _ = Empty +> treeTakeDepth n (Node x left right) = let +> nl = treeTakeDepth (n-1) left +> nr = treeTakeDepth (n-1) right +> in +> Node x nl nr + +See what occurs for this program: + + +main = print $ treeTakeDepth 4 nullTree + + +This code compiles, runs and stops giving the following result: + +~~~ +< 0 +: |-- 0 +: | |-- 0 +: | | |-- 0 +: | | `-- 0 +: | `-- 0 +: | |-- 0 +: | `-- 0 +: `-- 0 +: |-- 0 +: | |-- 0 +: | `-- 0 +: `-- 0 +: |-- 0 +: `-- 0 +~~~ + +Just to heat up your neurones a bit more, +let's make a slightly more interesting tree: + +> iTree = Node 0 (dec iTree) (inc iTree) +> where +> dec (Node x l r) = Node (x-1) (dec l) (dec r) +> inc (Node x l r) = Node (x+1) (inc l) (inc r) + +Another way to create this tree is to use a higher order function. +This function should be similar to `map`, but should work on `BinTree` instead of list. +Here is such a function: + +> -- apply a function to each node of Tree +> treeMap :: (a -> b) -> BinTree a -> BinTree b +> treeMap f Empty = Empty +> treeMap f (Node x left right) = Node (f x) +> (treeMap f left) +> (treeMap f right) + +_Hint_: I won't talk more about this here. +If you are interested by the generalization of `map` to other data structures, +search for functor and `fmap`. + +Our definition is now: + +> infTreeTwo :: BinTree Int +> infTreeTwo = Node 0 (treeMap (\x -> x-1) infTreeTwo) +> (treeMap (\x -> x+1) infTreeTwo) + +Look at the result for + + +main = print $ treeTakeDepth 4 infTreeTwo + + +~~~ +< 0 +: |-- -1 +: | |-- -2 +: | | |-- -3 +: | | `-- -1 +: | `-- 0 +: | |-- -1 +: | `-- 1 +: `-- 1 +: |-- 0 +: | |-- -1 +: | `-- 1 +: `-- 2 +: |-- 1 +: `-- 3 +~~~ + + +
    + +> main = do +> print $ treeTakeDepth 4 nullTree +> print $ treeTakeDepth 4 infTreeTwo + +
    diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/Complex.h b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/Complex.h new file mode 100644 index 0000000..59fd9a5 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/Complex.h @@ -0,0 +1,23 @@ +#include +using namespace std; +class Complex +{ +public: + double real_; + double img_; + + Complex(double real, double img) : real_(real), img_(img) {} + + // overloaded the multiplication operator + Complex operator*(Complex z) + { + return Complex(real_*z.real_ - img_*z.img_, + img_*z.real_ + real_*z.img_); + } +}; +// to print Complexes +ostream& operator<<(ostream& os, const Complex& z) { + os << z.real_ << " + " << z.img_ << "i"; + return os; +} + diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/a.out b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/a.out new file mode 100755 index 0000000..27e064a Binary files /dev/null and b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/a.out differ diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/liste.c b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/liste.c new file mode 100644 index 0000000..c20019e --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/liste.c @@ -0,0 +1,75 @@ +#include +#include + +int evenSum(int *list) { + return accumSum(0,list); +} + +int accumSum(int n, int *list) { + int x; + int *xs; + if (*list == NULL) { // if the list is empty + return n; + } else { + x = list[0]; // let x be the first element of the list + xs = list+1; // let xs be the list without x + if ( 0 == (x%2) ) { // if x is even + return accumSum(n+x, xs); + } else { + return accumSum(n, xs); + } + } +} + +// Create the list [1..n] +int *createList(int n) { + int *list = malloc((n+1) * sizeof(int)); + int i; + for (i=0; i= p) { + res++; + p*=10; + } + list++; + } + // I don't remove the two additional char values + // because I must take into account [ and ] + return res; +} + +// show the list +char *showList(int *list) { + int len=strLengthForList(list); + char *result = (char *)malloc( len ); + *result='\0'; + + while (*list) { + if (*result) + sprintf(result, "%s, %d", result, *list); + else + sprintf(result, "[%d", *list); + list++; + } + result[len-1]=']'; + result[len]='\0'; + return result; +} + +int main(int argc, char **argv) { + int *list = createList(5); + printf("evenSum of %s is %d\n", showList(list), evenSum(list)); + return 0; +} diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/type_inference.cpp b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/type_inference.cpp new file mode 100644 index 0000000..da54025 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/type_inference.cpp @@ -0,0 +1,21 @@ +#include +#include +using namespace std; + +template +T square(T x) +{ + return x*x; +} + +int main() { + // int + int sqr_of_five = square(5); + cout << sqr_of_five << endl; + // double + cout << (double)square(5.3) << endl; + // complex + cout << square( complex(5,3) ) + << endl; + return 0; +} diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/00_Introduction.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/00_Introduction.lhs new file mode 100644 index 0000000..d91542d --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/00_Introduction.lhs @@ -0,0 +1,16 @@ +

    Hell Difficulty Part

    + +Congratulations for getting so far! +Now, some of the really hardcore stuff can start. + +If you are like me, you should get the functional style. +You should also understand a bit more the advantages of laziness by default. +But you also don't really understand where to start in order to make a real +program. +And in particular: + +- How do you deal with effects? +- Why is there a strange imperative-like notation for dealing with IO? + +Be prepared, the answers might be complex. +But they all be very rewarding. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/01_progressive_io_example.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/01_progressive_io_example.lhs new file mode 100644 index 0000000..1d81132 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/01_progressive_io_example.lhs @@ -0,0 +1,93 @@ +

    Deal With IO

    + +<%= blogimage("magritte_carte_blanche.jpg","Magritte, Carte blanche") %> + + > <%=tldr%> + > + > A typical function doing `IO` looks a lot like an imperative program: + > + > ~~~ + > f :: IO a + > f = do + > x <- action1 + > action2 x + > y <- action3 + > action4 x y + > ~~~ + > + > - To set a value to an object we use `<-` . + > - The type of each line is `IO *`; + > in this example: + > - `action1 :: IO b` + > - `action2 x :: IO ()` + > - `action3 :: IO c` + > - `action4 x y :: IO a` + > - `x :: b`, `y :: c` + > - Few objects have the type `IO a`, this should help you choose. + > In particular you cannot use pure functions directly here. + > To use pure functions you could do `action2 (purefunction x)` for example. + +In this section, I will explain how to use IO, not how it works. +You'll see how Haskell separates the pure from the impure parts of the program. + +Don't stop because you're trying to understand the details of the syntax. +Answers will come in the next section. + +What to achieve? + + > Ask a user to enter a list of numbers. + > Print the sum of the numbers + +> toList :: String -> [Integer] +> toList input = read ("[" ++ input ++ "]") +> +> main = do +> putStrLn "Enter a list of numbers (separated by comma):" +> input <- getLine +> print $ sum (toList input) + +It should be straightforward to understand the behavior of this program. +Let's analyze the types in more detail. + +~~~ +putStrLn :: String -> IO () +getLine :: IO String +print :: Show a => a -> IO () +~~~ + +Or more interestingly, we note that each expression in the `do` block has a type of `IO a`. + +
    +main = do
    +  putStrLn "Enter ... " :: IO ()
    +  getLine               :: IO String
    +  print Something       :: IO ()
    +
    + +We should also pay attention to the effect of the `<-` symbol. + +~~~ +do + x <- something +~~~ + +If `something :: IO a` then `x :: a`. + +Another important note about using `IO`. +All lines in a do block must be of one of the two forms: + +~~~ +action1 :: IO a + -- in this case, generally a = () +~~~ + +or + +~~~ +value <- action2 -- where + -- bar z t :: IO b + -- value :: b +~~~ + +These two kinds of line will correspond to two different ways of sequencing actions. +The meaning of this sentence should be clearer by the end of the next section. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/02_progressive_io_example.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/02_progressive_io_example.lhs new file mode 100644 index 0000000..b77f1b7 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/02_progressive_io_example.lhs @@ -0,0 +1,84 @@ +Now let's see how this program behaves. +For example, what occur if the user enter something strange? +Let's try: + +~~~ + % runghc 02_progressive_io_example.lhs + Enter a list of numbers (separated by comma): + foo + Prelude.read: no parse +~~~ + +Argh! An evil error message and a crash! +The first evolution will be to answer with a more friendly message. + +In order to do this, we must detect that something went wrong. +Here is one way to do this. +Use the type `Maybe`. +It is a very common type in Haskell. + +> import Data.Maybe + +What is this thing? `Maybe` is a type which takes one parameter. +Its definition is: + + +data Maybe a = Nothing | Just a + + +This is a nice way to tell there was an error while trying to create/compute +a value. +The `maybeRead` function is a great example of this. +This is a function similar to the function `read`[^1], +but if something goes wrong the returned value is `Nothing`. +If the value is right, it returns `Just `. +Don't try to understand too much of this function. +I use a lower level function than `read`; `reads`. + +[^1]: Which itself is very similar to the javascript `eval` on a string containing JSON). + +> maybeRead :: Read a => String -> Maybe a +> maybeRead s = case reads s of +> [(x,"")] -> Just x +> _ -> Nothing + +Now to be a bit more readable, we define a function which goes like this: +If the string has the wrong format, it will return `Nothing`. +Otherwise, for example for "1,2,3", it will return `Just [1,2,3]`. + +> getListFromString :: String -> Maybe [Integer] +> getListFromString str = maybeRead $ "[" ++ str ++ "]" + + +We simply have to test the value in our main function. + +> main :: IO () +> main = do +> putStrLn "Enter a list of numbers (separated by comma):" +> input <- getLine +> let maybeList = getListFromString input in +> case maybeList of +> Just l -> print (sum l) +> Nothing -> error "Bad format. Good Bye." + +In case of error, we display a nice error message. + +Note that the type of each expression in the main's do block remains of the form `IO a`. +The only strange construction is `error`. +I'll say `error msg` will simply take the needed type (here `IO ()`). + +One very important thing to note is the type of all the functions defined so far. +There is only one function which contains `IO` in its type: `main`. +This means main is impure. +But main uses `getListFromString` which is pure. +It is then clear just by looking at declared types which functions are pure and +which are impure. + +Why does purity matter? +I certainly forget many advantages, but the three main reasons are: + +- It is far easier to think about pure code than impure one. +- Purity protects you from all the hard to reproduce bugs due to side effects. +- You can evaluate pure functions in any order or in parallel without risk. + +This is why you should generally put as most code as possible inside pure functions. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/03_progressive_io_example.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/03_progressive_io_example.lhs new file mode 100644 index 0000000..5d4ffe3 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/03_progressive_io_example.lhs @@ -0,0 +1,58 @@ +Our next evolution will be to prompt the user again and again until she enters a valid answer. + +We keep the first part: + +> import Data.Maybe +> +> maybeRead :: Read a => String -> Maybe a +> maybeRead s = case reads s of +> [(x,"")] -> Just x +> _ -> Nothing +> getListFromString :: String -> Maybe [Integer] +> getListFromString str = maybeRead $ "[" ++ str ++ "]" + +Now, we create a function which will ask the user for an list of integers +until the input is right. + +> askUser :: IO [Integer] +> askUser = do +> putStrLn "Enter a list of numbers (separated by comma):" +> input <- getLine +> let maybeList = getListFromString input in +> case maybeList of +> Just l -> return l +> Nothing -> askUser + +This function is of type `IO [Integer]`. +Such a type means that we retrieved a value of type `[Integer]` through some IO actions. +Some people might explain while waving their hands: + + > «This is an `[Integer]` inside an `IO`» + +If you want to understand the details behind all of this, you'll have to read the next section. +But sincerely, if you just want to _use_ IO. +Just practice a little and remember to think about the type. + +Finally our main function is quite simpler: + +> main :: IO () +> main = do +> list <- askUser +> print $ sum list + +We have finished with our introduction to `IO`. +This was quite fast. Here are the main things to remember: + +- in the `do` bloc, each expression must have the type `IO a`. + You are then limited in the number of expressions available. + For example, `getLine`, `print`, `putStrLn`, etc... +- Try to externalize the pure functions as much as possible. +- the `IO a` type means: an IO _action_ which returns an element of type `a`. + `IO` represents actions; under the hood, `IO a` is the type of a function. + Read the next section if you are curious. + +If you practice a bit, you should be able to _use_ `IO`. + + > _Exercises_: + > + > - Make a program that sums all of its arguments. Hint: use the function `getArgs`. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/20_Detailled_IO.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/20_Detailled_IO.lhs new file mode 100644 index 0000000..0a806cf --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/20_Detailled_IO.lhs @@ -0,0 +1,422 @@ +

    IO trick explained

    + +<%= blogimage("magritte_pipe.jpg","Magritte, ceci n'est pas une pipe") %> + + > Here is a <%=tldr%> for this section. + > + > To separate pure and impure parts, + > `main` is defined as a function + > which modifies the state of the world + > + > ~~~ + > main :: World -> World + > ~~~ + > + > A function is guaranteed to have side effects only if it has this type. + > But look at a typical main function: + > + > ~~~ + > main w0 = + > let (v1,w1) = action1 w0 in + > let (v2,w2) = action2 v1 w1 in + > let (v3,w3) = action3 v2 w2 in + > action4 v3 w3 + > ~~~ + > + > We have a lot of temporary elements (here `w1`, `w2` and `w3`) + > which must be passed on to the next action. + > + > We create a function `bind` or `(>>=)`. + > With `bind` we don't need temporary names anymore. + > + > ~~~ + > main = + > action1 >>= action2 >>= action3 >>= action4 + > ~~~ + > + > Bonus: Haskell has syntactical sugar for us: + > + > ~~~ + > main = do + > v1 <- action1 + > v2 <- action2 v1 + > v3 <- action3 v2 + > action4 v3 + > ~~~ + + +Why did we use this strange syntax, and what exactly is this `IO` type? +It looks a bit like magic. + +For now let's just forget all about the pure parts of our program, and focus +on the impure parts: + + +askUser :: IO [Integer] +askUser = do + putStrLn "Enter a list of numbers (separated by commas):" + input <- getLine + let maybeList = getListFromString input in + case maybeList of + Just l -> return l + Nothing -> askUser + +main :: IO () +main = do + list <- askUser + print $ sum list + + +First remark; it looks like an imperative structure. +Haskell is powerful enough to make impure code look imperative. +For example, if you wish you could create a `while` in Haskell. +In fact, for dealing with `IO`, imperative style is generally more appropriate. + +But you should had noticed the notation is a bit unusual. +Here is why, in detail. + +In an impure language, the state of the world can be seen as a huge hidden global variable. +This hidden variable is accessible by all functions of your language. +For example, you can read and write a file in any function. +The fact that a file exists or not can be seen as different states of the world. + +For Haskell this state is not hidden. +It is explicitly said `main` is a function that _potentially_ changes the state of the world. +Its type is then something like: + + +main :: World -> World + + +Not all functions may have access to this variable. +Those which have access to this variable are impure. +Functions to which the world variable isn't provided are pure[^032001]. + +[^032001]: There are some _unsafe_ exceptions to this rule. But you shouldn't see such use on a real application except maybe for debugging purpose. + +Haskell considers the state of the world as an input variable to `main`. +But the real type of main is closer to this one[^032002]: + +[^032002]: For the curious the real type is `data IO a = IO {unIO :: State# RealWorld -> (# State# RealWorld, a #)}`. All the `#` as to do with optimisation and I swapped the fields in my example. But mostly, the idea is exactly the same. + + +main :: World -> ((),World) + + +The `()` type is the null type. +Nothing to see here. + +Now let's rewrite our main function with this in mind: + + +main w0 = + let (list,w1) = askUser w0 in + let (x,w2) = print (sum list,w1) in + x + + +First, we note that all functions which have side effects must have the type: + + +World -> (a,World) + + +Where `a` is the type of the result. +For example, a `getChar` function should have the type `World -> (Char,World)`. + +Another thing to note is the trick to fix the order of evaluation. +In Haskell, in order to evaluate `f a b`, you have many choices: + +- first eval `a` then `b` then `f a b` +- first eval `b` then `a` then `f a b`. +- eval `a` and `b` in parallel then `f a b` + +This is true, because we should work in a pure language. + +Now, if you look at the main function, it is clear you must eval the first +line before the second one since, to evaluate the second line you have +to get a parameter given by the evaluation of the first line. + +Such trick works nicely. +The compiler will at each step provide a pointer to a new real world id. +Under the hood, `print` will evaluate as: + +- print something on the screen +- modify the id of the world +- evaluate as `((),new world id)`. + +Now, if you look at the style of the main function, it is clearly awkward. +Let's try to do the same to the askUser function: + + +askUser :: World -> ([Integer],World) + + +Before: + + +askUser :: IO [Integer] +askUser = do + putStrLn "Enter a list of numbers:" + input <- getLine + let maybeList = getListFromString input in + case maybeList of + Just l -> return l + Nothing -> askUser + + +After: + + +askUser w0 = + let (_,w1) = putStrLn "Enter a list of numbers:" in + let (input,w2) = getLine w1 in + let (l,w3) = case getListFromString input of + Just l -> (l,w2) + Nothing -> askUser w2 + in + (l,w3) + + +This is similar, but awkward. +Look at all these temporary `w?` names. + +The lesson, is, naive IO implementation in Pure functional languages is awkward! + +Fortunately, there is a better way to handle this problem. +We see a pattern. +Each line is of the form: + + +let (y,w') = action x w in + + +Even if for some line the first `x` argument isn't needed. +The output type is a couple, `(answer, newWorldValue)`. +Each function `f` must have a type similar to: + + +f :: World -> (a,World) + + +Not only this, but we can also note that we always follow the same usage pattern: + + +let (y,w1) = action1 w0 in +let (z,w2) = action2 w1 in +let (t,w3) = action3 w2 in +... + + +Each action can take from 0 to n parameters. +And in particular, each action can take a parameter from the result of a line above. + +For example, we could also have: + + +let (_,w1) = action1 x w0 in +let (z,w2) = action2 w1 in +let (_,w3) = action3 x z w2 in +... + + +And of course `actionN w :: (World) -> (a,World)`. + + > IMPORTANT, there are only two important patterns to consider: + > + > ~~~ + > let (x,w1) = action1 w0 in + > let (y,w2) = action2 x w1 in + > ~~~ + > + > and + > + > ~~~ + > let (_,w1) = action1 w0 in + > let (y,w2) = action2 w1 in + > ~~~ + +<%= leftblogimage("jocker_pencil_trick.jpg","Jocker pencil trick") %> + +Now, we will do a magic trick. +We will make the temporary world symbol "disappear". +We will `bind` the two lines. +Let's define the `bind` function. +Its type is quite intimidating at first: + + +bind :: (World -> (a,World)) + -> (a -> (World -> (b,World))) + -> (World -> (b,World)) + + +But remember that `(World -> (a,World))` is the type for an IO action. +Now let's rename it for clarity: + + +type IO a = World -> (a, World) + + +Some example of functions: + + +getLine :: IO String +print :: Show a => a -> IO () + + +`getLine` is an IO action which takes a world as parameter and returns a couple `(String,World)`. +Which can be summarized as: `getLine` is of type `IO String`. +Which we also see as, an IO action which will return a String "embeded inside an IO". + +The function `print` is also interesting. +It takes one argument which can be shown. +In fact it takes two arguments. +The first is the value to print and the other is the state of world. +It then returns a couple of type `((),World)`. +This means it changes the state of the world, but doesn't yield anymore data. + +This type helps us simplify the type of `bind`: + + +bind :: IO a + -> (a -> IO b) + -> IO b + + +It says that `bind` takes two IO actions as parameter and return another IO action. + +Now, remember the _important_ patterns. The first was: + + +let (x,w1) = action1 w0 in +let (y,w2) = action2 x w1 in +(y,w2) + + +Look at the types: + + +action1 :: IO a +action2 :: a -> IO b +(y,w2) :: IO b + + +Doesn't it seem familiar? + + +(bind action1 action2) w0 = + let (x, w1) = action1 w0 + (y, w2) = action2 x w1 + in (y, w2) + + +The idea is to hide the World argument with this function. Let's go: +As an example imagine if we wanted to simulate: + + +let (line1,w1) = getLine w0 in +let ((),w2) = print line1 in +((),w2) + + +Now, using the bind function: + + +(res,w2) = (bind getLine (\l -> print l)) w0 + + +As print is of type (World -> ((),World)), we know res = () (null type). +If you didn't see what was magic here, let's try with three lines this time. + + + +let (line1,w1) = getLine w0 in +let (line2,w2) = getLine w1 in +let ((),w3) = print (line1 ++ line2) in +((),w3) + + +Which is equivalent to: + + +(res,w3) = bind getLine (\line1 -> + bind getLine (\line2 -> + print (line1 ++ line2))) + + +Didn't you notice something? +Yes, no temporary World variables are used anywhere! +This is _MA_. _GIC_. + +We can use a better notation. +Let's use `(>>=)` instead of `bind`. +`(>>=)` is an infix function like +`(+)`; reminder `3 + 4 ⇔ (+) 3 4` + + +(res,w3) = getLine >>= + \line1 -> getLine >>= + \line2 -> print (line1 ++ line2) + + +Ho Ho Ho! Happy Christmas Everyone! +Haskell has made syntactical sugar for us: + + +do + x <- action1 + y <- action2 + z <- action3 + ... + + +Is replaced by: + + +action1 >>= \x -> +action2 >>= \y -> +action3 >>= \z -> +... + + +Note you can use `x` in `action2` and `x` and `y` in `action3`. + +But what about the lines not using the `<-`? +Easy, another function `blindBind`: + + +blindBind :: IO a -> IO b -> IO b +blindBind action1 action2 w0 = + bind action (\_ -> action2) w0 + + +I didn't simplify this definition for clarity purpose. +Of course we can use a better notation, we'll use the `(>>)` operator. + +And + + +do + action1 + action2 + action3 + + +Is transformed into + + +action1 >> +action2 >> +action3 + + +Also, another function is quite useful. + + +putInIO :: a -> IO a +putInIO x = IO (\w -> (x,w)) + + +This is the general way to put pure values inside the "IO context". +The general name for `putInIO` is `return`. +This is quite a bad name when you learn Haskell. `return` is very different from what you might be used to. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/21_Detailled_IO.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/21_Detailled_IO.lhs new file mode 100644 index 0000000..27578a6 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/21_Detailled_IO.lhs @@ -0,0 +1,45 @@ +To finish, let's translate our example: + + + +askUser :: IO [Integer] +askUser = do + putStrLn "Enter a list of numbers (separated by commas):" + input <- getLine + let maybeList = getListFromString input in + case maybeList of + Just l -> return l + Nothing -> askUser + +main :: IO () +main = do + list <- askUser + print $ sum list + + +Is translated into: + +> import Data.Maybe +> +> maybeRead :: Read a => String -> Maybe a +> maybeRead s = case reads s of +> [(x,"")] -> Just x +> _ -> Nothing +> getListFromString :: String -> Maybe [Integer] +> getListFromString str = maybeRead $ "[" ++ str ++ "]" +> askUser :: IO [Integer] +> askUser = +> putStrLn "Enter a list of numbers (sep. by commas):" >> +> getLine >>= \input -> +> let maybeList = getListFromString input in +> case maybeList of +> Just l -> return l +> Nothing -> askUser +> +> main :: IO () +> main = askUser >>= +> \list -> print $ sum list + +You can compile this code to verify it keeps working. + +Imagine what it would look like without the `(>>)` and `(>>=)`. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/30_hardcore_IO b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/30_hardcore_IO new file mode 100644 index 0000000..d5c8cb0 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/30_hardcore_IO @@ -0,0 +1,66 @@ +First, let's take a look at the IO type. + +> newtype IO a = IO (State# RealWorld -> (# State# RealWorld,a #)) + +Oh gosh! What is this notation? + +I said it will be Haskell the hard way. It is. +Now make it easier. + +First, we need to understand some basic concepts. +You can just forget about all these sharp symbols. + +> newtype IO a = IO (State RealWorld -> (State RealWorld, a)) + +OK, let's start here. What is `RealWorld`? +From the doc + +> data RealWorld + +RealWorld is deeply magical. +It is primitive, but it is not unlifted (hence ptrArg). +We never manipulate values of type RealWorld; it's only used in the type system, to parameterise `State#`. + +Uh? It is a type with nothing inside it. +No representation for it at all. +It is just a name. + +Now what is `State`? + +It is a data with one parameter, a type of state. + +> data State s + +The only purpose of the type parameter (`s`) is to keep different state threads separate. + +In fact, let's try to translate + +> newtype IO a = IO (State RealWorld -> (State RealWorld, a)) + +In a more Human and intuitive terms with an example. + +> IO String = IO (State RealWorld -> (State RealWorld, String)) + +`IO String` is a function from a type (State RealWorld) to a couple +of type (State RealWorld, String). + +Which if we simplify another time can be said as: + +IO String is a function which turns one state of the world into another state of the world and an String value. + +It seems to fit nicely with a function like `getLine`. + +`getLine` can be a function to which we provide a state of the real world. +Then `getLine` does its job, then after this, it provides us with a couple of +values. +The String containing the content of the line read, and the changed world state. +Changed because, when we had read something, the state of the world changed. + +Congratulations for having followed the first _Hardcore Haskell IO_ level 1. + +Now, it is time to go to level 2. + +To make things clearer, I will increase the verbosity of the type. And instead of writing `IO a`. I will write `World -> (World,a)`. + +It was a nice help for me. +It is often hard to remember that "IO a" is in fact a function. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/10_Monads.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/10_Monads.lhs new file mode 100644 index 0000000..5419bd8 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/10_Monads.lhs @@ -0,0 +1,94 @@ +

    Monads

    + +<%= blogimage("dali_reve.jpg","Dali, reve. It represents a weapon out of the mouth of a tiger, itself out of the mouth of another tiger, itself out of the mouth of a fish itself out of a grenade. I could have choosen a picture of the Human centipede as it is a very good representation of what a monad really is. But just to thing about it, I find this disgusting and that wasn't the purpose of this document.") %> + +Now the secret can be revealed: `IO` is a _monad_. +Being a monad means you have access to some syntactical sugar with the `do` notation. +But mainly, you have access to a coding pattern which will ease the flow of your code. + + > **Important remarks**: + > + > - Monad are not necessarily about effects! + > There are a lot of _pure_ monads. + > - Monad are more about sequencing + +For the Haskell language `Monad` is a type class. +To be an instance of this type class, you must provide the functions `(>>=)` and `return`. +The function `(>>)` will be derived from `(>>=)`. +Here is how the type class `Monad` is declared (mostly): + + +class Monad m where + (>>=) :: m a -> (a -> m b) -> m b + return :: a -> m a + + (>>) :: m a -> m b -> m b + f >> g = f >>= \_ -> g + + -- You should generally safely ignore this function + -- which I believe exists for historical reason + fail :: String -> m a + fail = error + + + + > Remarks: + > + > - the keyword `class` is not your friend. + > A Haskell class is _not_ a class like in object model. + > A Haskell class has a lot of similarities with Java interfaces. + > A better word should have been `typeclass`. + > That means a set of types. + > For a type to belong to a class, all functions of the class must be provided for this type. + > - In this particular example of type class, the type `m` must be a type that takes an argument. + > for example `IO a`, but also `Maybe a`, `[a]`, etc... + > - To be a useful monad, your function must obey some rules. + > If your construction does not obey these rules strange things might happens: + > + > ~~~ + > return a >>= k == k a + > m >>= return == m + > m >>= (\x -> k x >>= h) == (m >>= k) >>= h + > ~~~ + +

    Maybe is a monad

    + +There are a lot of different types that are instance of `Monad`. +One of the easiest to describe is `Maybe`. +If you have a sequence of `Maybe` values, you can use monads to manipulate them. +It is particularly useful to remove very deep `if..then..else..` constructions. + +Imagine a complex bank operation. You are eligible to gain about 700€ only +if you can afford to follow a list of operations without being negative. + +> deposit value account = account + value +> withdraw value account = account - value +> +> eligible :: (Num a,Ord a) => a -> Bool +> eligible account = +> let account1 = deposit 100 account in +> if (account1 < 0) +> then False +> else +> let account2 = withdraw 200 account1 in +> if (account2 < 0) +> then False +> else +> let account3 = deposit 100 account2 in +> if (account3 < 0) +> then False +> else +> let account4 = withdraw 300 account3 in +> if (account4 < 0) +> then False +> else +> let account5 = deposit 1000 account4 in +> if (account5 < 0) +> then False +> else +> True +> +> main = do +> print $ eligible 300 -- True +> print $ eligible 299 -- False + diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/11_Monads.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/11_Monads.lhs new file mode 100644 index 0000000..445ffef --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/11_Monads.lhs @@ -0,0 +1,22 @@ +Now, let's make it better using Maybe and the fact that it is a Monad + +> deposit :: (Num a) => a -> a -> Maybe a +> deposit value account = Just (account + value) +> +> withdraw :: (Num a,Ord a) => a -> a -> Maybe a +> withdraw value account = if (account < value) +> then Nothing +> else Just (account - value) +> +> eligible :: (Num a, Ord a) => a -> Maybe Bool +> eligible account = do +> account1 <- deposit 100 account +> account2 <- withdraw 200 account1 +> account3 <- deposit 100 account2 +> account4 <- withdraw 300 account3 +> account5 <- deposit 1000 account4 +> Just True +> +> main = do +> print $ eligible 300 -- Just True +> print $ eligible 299 -- Nothing diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/12_Monads.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/12_Monads.lhs new file mode 100644 index 0000000..7f88b8c --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/12_Monads.lhs @@ -0,0 +1,38 @@ +Not bad, but we can make it even better: + +> deposit :: (Num a) => a -> a -> Maybe a +> deposit value account = Just (account + value) +> +> withdraw :: (Num a,Ord a) => a -> a -> Maybe a +> withdraw value account = if (account < value) +> then Nothing +> else Just (account - value) +> +> eligible :: (Num a, Ord a) => a -> Maybe Bool +> eligible account = +> deposit 100 account >>= +> withdraw 200 >>= +> deposit 100 >>= +> withdraw 300 >>= +> deposit 1000 >> +> return True +> +> main = do +> print $ eligible 300 -- Just True +> print $ eligible 299 -- Nothing + +We have proven that Monads are a good way to make our code more elegant. +Note this idea of code organization, in particular for `Maybe` can be used +in most imperative language. +In fact, this is the kind of construction we make naturally. + + > An important remark: + > + > The first element in the sequence being evaluated to `Nothing` will stop + > the complete evaluation. + > This means you don't execute all lines. + > You have this for free, thanks to laziness. + +The `Maybe` monad proved to be useful while being a very simple example. +We saw the utility of the `IO` monad. +But now a cooler example, lists. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/13_Monads.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/13_Monads.lhs new file mode 100644 index 0000000..d4874ac --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/13_Monads.lhs @@ -0,0 +1,53 @@ +

    The list monad

    + +<%= blogimage("golconde.jpg","Golconde de Magritte") %> + +The list monad helps us to simulate non deterministic computations. +Here we go: + +> import Control.Monad (guard) +> +> allCases = [1..10] +> +> resolve :: [(Int,Int,Int)] +> resolve = do +> x <- allCases +> y <- allCases +> z <- allCases +> guard $ 4*x + 2*y < z +> return (x,y,z) +> +> main = do +> print resolve + + +MA. GIC. : + +~~~ +[(1,1,7),(1,1,8),(1,1,9),(1,1,10),(1,2,9),(1,2,10)] +~~~ + +For the list monad, there is also a syntactical sugar: + +> print $ [ (x,y,z) | x <- allCases, +> y <- allCases, +> z <- allCases, +> 4*x + 2*y < z ] + +I won't list all the monads, but there are many monads. +Using monads simplifies the manipulation of several notions in pure languages. +In particular, monad are very useful for: + +- IO, +- non deterministic computation, +- generating pseudo random numbers, +- keeping configuration state, +- writing state, +- ... + +If you have followed me until here, then you've done it! +You know monads[^03021301]! + +[^03021301]: Well, you'll certainly need to practice a bit to get used to them +and to understand when you can use them and create your own. But you already +made a big step in this direction. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/03_More_on_infinite_trees/10_Infinite_Trees.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/03_More_on_infinite_trees/10_Infinite_Trees.lhs new file mode 100644 index 0000000..3a08dac --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/03_More_on_infinite_trees/10_Infinite_Trees.lhs @@ -0,0 +1,133 @@ +

    More on Infinite Tree

    + +In the section [Infinite Structures](#infinite-structures) we saw some simple construction. +Unfortunately we removed two properties of our tree: + +1. no duplicate node value +2. well ordered tree + +In this section we will try to keep the first property. +Concerning the second one, we must relax this one but we'll discuss on how to +keep it as much as possible. + +
    + +This code is mostly the same as the one in the [tree section](#trees). + +> import Data.List +> data BinTree a = Empty +> | Node a (BinTree a) (BinTree a) +> deriving (Eq,Ord) +> +> -- declare BinTree a to be an instance of Show +> instance (Show a) => Show (BinTree a) where +> -- will start by a '<' before the root +> -- and put a : a begining of line +> show t = "< " ++ replace '\n' "\n: " (treeshow "" t) +> where +> treeshow pref Empty = "" +> treeshow pref (Node x Empty Empty) = +> (pshow pref x) +> +> treeshow pref (Node x left Empty) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "`--" " " left) +> +> treeshow pref (Node x Empty right) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "`--" " " right) +> +> treeshow pref (Node x left right) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "|--" "| " left) ++ "\n" ++ +> (showSon pref "`--" " " right) +> +> -- show a tree using some prefixes to make it nice +> showSon pref before next t = +> pref ++ before ++ treeshow (pref ++ next) t +> +> -- pshow replace "\n" by "\n"++pref +> pshow pref x = replace '\n' ("\n"++pref) (show x) +> +> -- replace on char by another string +> replace c new string = +> concatMap (change c new) string +> where +> change c new x +> | x == c = new +> | otherwise = x:[] -- "x" +> +
    + +Our first step is to create some pseudo-random number list: + +> shuffle = map (\x -> (x*3123) `mod` 4331) [1..] + +Just as reminder here are the definition of `treeFromList` + +> treeFromList :: (Ord a) => [a] -> BinTree a +> treeFromList [] = Empty +> treeFromList (x:xs) = Node x (treeFromList (filter ( (treeFromList (filter (>x) xs)) + +and `treeTakeDepth`: + +> treeTakeDepth _ Empty = Empty +> treeTakeDepth 0 _ = Empty +> treeTakeDepth n (Node x left right) = let +> nl = treeTakeDepth (n-1) left +> nr = treeTakeDepth (n-1) right +> in +> Node x nl nr + +See the result of: + +> main = do +> putStrLn "take 10 shuffle" +> print $ take 10 shuffle +> putStrLn "\ntreeTakeDepth 4 (treeFromList shuffle)" +> print $ treeTakeDepth 4 (treeFromList shuffle) + +~~~ +% runghc 02_Hard_Part/41_Infinites_Structures.lhs +take 10 shuffle +[3123,1915,707,3830,2622,1414,206,3329,2121,913] +treeTakeDepth 4 (treeFromList shuffle) + +< 3123 +: |--1915 +: | |--707 +: | | |--206 +: | | `--1414 +: | `--2622 +: | |--2121 +: | `--2828 +: `--3830 +: |--3329 +: | |--3240 +: | `--3535 +: `--4036 +: |--3947 +: `--4242 +~~~ + +Yay! It ends! +Beware thought, it will only work if you always have something to put into a branch. + +For example + + +treeTakeDepth 4 (treeFromList [1..]) + + +will loop forever. +Simply because, it will try to access the head of `filter (<1) [2..]`. +But filter is not smart enought to understand that the result is the empty list. + +Nonetheless, it is still a very cool example of what non strict program has to offer. + +Left as an exercise to the reader: + +- Could you prove that there exists some number `n` such that `treeTakeDepth n (treeFromList shuffle)` will enter in an infinite loop. +- Find an upper bound for `n`. +- Prove there is no `shuffle` list such that, for any depth, the program ends. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/03_More_on_infinite_trees/11_Infinite_Trees.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/03_More_on_infinite_trees/11_Infinite_Trees.lhs new file mode 100644 index 0000000..308003b --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/03_More_on_infinite_trees/11_Infinite_Trees.lhs @@ -0,0 +1,155 @@ + +
    + +This code is mostly the same as the preceeding one. + +> import Debug.Trace (trace) +> import Data.List +> data BinTree a = Empty +> | Node a (BinTree a) (BinTree a) +> deriving (Eq,Ord) + +> -- declare BinTree a to be an instance of Show +> instance (Show a) => Show (BinTree a) where +> -- will start by a '<' before the root +> -- and put a : a begining of line +> show t = "< " ++ replace '\n' "\n: " (treeshow "" t) +> where +> treeshow pref Empty = "" +> treeshow pref (Node x Empty Empty) = +> (pshow pref x) +> +> treeshow pref (Node x left Empty) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "`--" " " left) +> +> treeshow pref (Node x Empty right) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "`--" " " right) +> +> treeshow pref (Node x left right) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "|--" "| " left) ++ "\n" ++ +> (showSon pref "`--" " " right) +> +> -- show a tree using some prefixes to make it nice +> showSon pref before next t = +> pref ++ before ++ treeshow (pref ++ next) t +> +> -- pshow replace "\n" by "\n"++pref +> pshow pref x = replace '\n' ("\n"++pref) (" " ++ show x) +> +> -- replace on char by another string +> replace c new string = +> concatMap (change c new) string +> where +> change c new x +> | x == c = new +> | otherwise = x:[] -- "x" +> +> treeTakeDepth _ Empty = Empty +> treeTakeDepth 0 _ = Empty +> treeTakeDepth n (Node x left right) = let +> nl = treeTakeDepth (n-1) left +> nr = treeTakeDepth (n-1) right +> in +> Node x nl nr + +
    + +In order to resolve these problem we will modify slightly our +`treeFromList` and `shuffle` function. + +A first problem, is the lack of infinite different number in our implementation of `shuffle`. +We generated only `4331` different numbers. +To resolve this we make a slightly better `shuffle` function. + +> shuffle = map rand [1..] +> where +> rand x = ((p x) `mod` (x+c)) - ((x+c) `div` 2) +> p x = m*x^2 + n*x + o -- some polynome +> m = 3123 +> n = 31 +> o = 7641 +> c = 1237 + +This shuffle function has the property (hopefully) not to have an upper nor lower bound. +But having a better shuffle list isn't enough not to enter an infinite loop. + +Generally, we cannot decide whether `filter ( Any element of the left (resp. right) branch must all be strictly inferior (resp. superior) to the label of the root. + +Remark it will remains _mostly_ an ordered binary tree. +Furthermore, by construction, each node value is unique in the tree. + +Here is our new version of `treeFromList`. We simply have replaced `filter` by `safefilter`. + +> treeFromList :: (Ord a, Show a) => [a] -> BinTree a +> treeFromList [] = Empty +> treeFromList (x:xs) = Node x left right +> where +> left = treeFromList $ safefilter ( right = treeFromList $ safefilter (>x) xs + +This new function `safefilter` is almost equivalent to `filter` but don't enter infinite loop if the result is a finite list. +If it cannot find an element for which the test is true after 10000 consecutive steps, then it considers to be the end of the search. + +> safefilter :: (a -> Bool) -> [a] -> [a] +> safefilter f l = safefilter' f l nbTry +> where +> nbTry = 10000 +> safefilter' _ _ 0 = [] +> safefilter' _ [] _ = [] +> safefilter' f (x:xs) n = +> if f x +> then x : safefilter' f xs nbTry +> else safefilter' f xs (n-1) + +Now run the program and be happy: + +> main = do +> putStrLn "take 10 shuffle" +> print $ take 10 shuffle +> putStrLn "\ntreeTakeDepth 8 (treeFromList shuffle)" +> print $ treeTakeDepth 8 (treeFromList $ shuffle) + +You should realize the time to print each value is different. +This is because Haskell compute each value when it needs it. +And in this case, this is when asked to print it on the screen. + +Impressively enough, try to replace the depth from `8` to `100`. +It will work without killing your RAM! +The flow and the memory management is done naturally by Haskell. + +Left as an exercise to the reader: + +- Even with large constant value for `deep` and `nbTry`, it seems to work nicely. But in the worst case, it can be exponential. + Create a worst case list to give as parameter to `treeFromList`. + _hint_: think about (`[0,-1,-1,....,-1,1,-1,...,-1,1,...]`). +- I first tried to implement `safefilter` as follow: +
    +  safefilter' f l = if filter f (take 10000 l) == []
    +                    then []
    +                    else filter f l
    +  
    + Explain why it doesn't work and can enter into an infinite loop. +- Suppose that `shuffle` is real random list with growing bounds. + If you study a bit this structure, you'll discover that with probability 1, + this structure is finite. + Using the following code + (suppose we could use `safefilter'` directly as if was not in the where of safefilter) + find a definition of `f` such that with probability `1`, + treeFromList' shuffle is infinite. And prove it. + Disclamer, this is only a conjecture. + + +treeFromList' [] n = Empty +treeFromList' (x:xs) n = Node x left right + where + left = treeFromList' (safefilter' (x) xs (f n) + f = ??? + diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/monad.hs b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/monad.hs new file mode 100644 index 0000000..ad499bf --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/monad.hs @@ -0,0 +1,38 @@ +-- Pure +import Control.Monad +import Data.List + +replace x y z + | z == x = y + | otherwise = z + +maybeRead :: Read a => String -> Maybe a +maybeRead s = case reads s of + [(x,"")] -> Just x + _ -> Nothing + +listFromString :: String -> Maybe [Int] +listFromString line = maybeRead $ "[" ++ intervirg line ++ "]" + where intervirg = map $ replace ' ' ',' + +-- Impure +showSum :: [Int] -> IO () +showSum list = do + putStr $ join $ intersperse " + " $ map show list + putStr " = " + +lookupList :: String -> IO [Int] +lookupList line = + let readList = listFromString line in + case readList of + Nothing -> error "I said integers!" + Just list -> return list + +main :: IO () +main = do + putStrLn "Enter a list of integers" + line <- getLine + list <- lookupList line + let slist = map (^2) list in do + showSum $ slist + print $ sum slist diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/00_appendix.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/00_appendix.lhs new file mode 100644 index 0000000..fe9f36f --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/00_appendix.lhs @@ -0,0 +1,4 @@ +

    Appendix

    + +This section is not so much about learning Haskell. +It is just here to discuss some details further. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs new file mode 100644 index 0000000..7a6e96e --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs @@ -0,0 +1,135 @@ +

    More on Infinite Tree

    + +In the section [Infinite Structures](#infinite-structures) we saw some simple +constructions. +Unfortunately we removed two properties from our tree: + +1. no duplicate node value +2. well ordered tree + +In this section we will try to keep the first property. +Concerning the second one, we must relax it but we'll discuss how to +keep it as much as possible. + +
    + +This code is mostly the same as the one in the [tree section](#trees). + +> import Data.List +> data BinTree a = Empty +> | Node a (BinTree a) (BinTree a) +> deriving (Eq,Ord) +> +> -- declare BinTree a to be an instance of Show +> instance (Show a) => Show (BinTree a) where +> -- will start by a '<' before the root +> -- and put a : a begining of line +> show t = "< " ++ replace '\n' "\n: " (treeshow "" t) +> where +> treeshow pref Empty = "" +> treeshow pref (Node x Empty Empty) = +> (pshow pref x) +> +> treeshow pref (Node x left Empty) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "`--" " " left) +> +> treeshow pref (Node x Empty right) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "`--" " " right) +> +> treeshow pref (Node x left right) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "|--" "| " left) ++ "\n" ++ +> (showSon pref "`--" " " right) +> +> -- show a tree using some prefixes to make it nice +> showSon pref before next t = +> pref ++ before ++ treeshow (pref ++ next) t +> +> -- pshow replace "\n" by "\n"++pref +> pshow pref x = replace '\n' ("\n"++pref) (show x) +> +> -- replace on char by another string +> replace c new string = +> concatMap (change c new) string +> where +> change c new x +> | x == c = new +> | otherwise = x:[] -- "x" +> + +
    + +Our first step is to create some pseudo-random number list: + +> shuffle = map (\x -> (x*3123) `mod` 4331) [1..] + +Just as a reminder, here is the definition of `treeFromList` + +> treeFromList :: (Ord a) => [a] -> BinTree a +> treeFromList [] = Empty +> treeFromList (x:xs) = Node x (treeFromList (filter ( (treeFromList (filter (>x) xs)) + +and `treeTakeDepth`: + +> treeTakeDepth _ Empty = Empty +> treeTakeDepth 0 _ = Empty +> treeTakeDepth n (Node x left right) = let +> nl = treeTakeDepth (n-1) left +> nr = treeTakeDepth (n-1) right +> in +> Node x nl nr + +See the result of: + +> main = do +> putStrLn "take 10 shuffle" +> print $ take 10 shuffle +> putStrLn "\ntreeTakeDepth 4 (treeFromList shuffle)" +> print $ treeTakeDepth 4 (treeFromList shuffle) + +~~~ +% runghc 02_Hard_Part/41_Infinites_Structures.lhs +take 10 shuffle +[3123,1915,707,3830,2622,1414,206,3329,2121,913] +treeTakeDepth 4 (treeFromList shuffle) + +< 3123 +: |--1915 +: | |--707 +: | | |--206 +: | | `--1414 +: | `--2622 +: | |--2121 +: | `--2828 +: `--3830 +: |--3329 +: | |--3240 +: | `--3535 +: `--4036 +: |--3947 +: `--4242 +~~~ + +Yay! It ends! +Beware though, it will only work if you always have something to put into a branch. + +For example + + +treeTakeDepth 4 (treeFromList [1..]) + + +will loop forever. +Simply because it will try to access the head of `filter (<1) [2..]`. +But `filter` is not smart enought to understand that the result is the empty list. + +Nonetheless, it is still a very cool example of what non strict programs have to offer. + +Left as an exercise to the reader: + +- Prove the existence of a number `n` so that `treeTakeDepth n (treeFromList shuffle)` will enter an infinite loop. +- Find an upper bound for `n`. +- Prove there is no `shuffle` list so that, for any depth, the program ends. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs new file mode 100644 index 0000000..9a5f9d5 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs @@ -0,0 +1,155 @@ + +
    + +This code is mostly the same as the preceding one. + +> import Debug.Trace (trace) +> import Data.List +> data BinTree a = Empty +> | Node a (BinTree a) (BinTree a) +> deriving (Eq,Ord) + +> -- declare BinTree a to be an instance of Show +> instance (Show a) => Show (BinTree a) where +> -- will start by a '<' before the root +> -- and put a : a begining of line +> show t = "< " ++ replace '\n' "\n: " (treeshow "" t) +> where +> treeshow pref Empty = "" +> treeshow pref (Node x Empty Empty) = +> (pshow pref x) +> +> treeshow pref (Node x left Empty) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "`--" " " left) +> +> treeshow pref (Node x Empty right) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "`--" " " right) +> +> treeshow pref (Node x left right) = +> (pshow pref x) ++ "\n" ++ +> (showSon pref "|--" "| " left) ++ "\n" ++ +> (showSon pref "`--" " " right) +> +> -- show a tree using some prefixes to make it nice +> showSon pref before next t = +> pref ++ before ++ treeshow (pref ++ next) t +> +> -- pshow replace "\n" by "\n"++pref +> pshow pref x = replace '\n' ("\n"++pref) (" " ++ show x) +> +> -- replace on char by another string +> replace c new string = +> concatMap (change c new) string +> where +> change c new x +> | x == c = new +> | otherwise = x:[] -- "x" +> +> treeTakeDepth _ Empty = Empty +> treeTakeDepth 0 _ = Empty +> treeTakeDepth n (Node x left right) = let +> nl = treeTakeDepth (n-1) left +> nr = treeTakeDepth (n-1) right +> in +> Node x nl nr + +
    + +In order to resolve these problem we will modify slightly our +`treeFromList` and `shuffle` function. + +A first problem, is the lack of infinite different number in our implementation of `shuffle`. +We generated only `4331` different numbers. +To resolve this we make a slightly better `shuffle` function. + +> shuffle = map rand [1..] +> where +> rand x = ((p x) `mod` (x+c)) - ((x+c) `div` 2) +> p x = m*x^2 + n*x + o -- some polynome +> m = 3123 +> n = 31 +> o = 7641 +> c = 1237 + +This shuffle function has the property (hopefully) not to have an upper nor lower bound. +But having a better shuffle list isn't enough not to enter an infinite loop. + +Generally, we cannot decide whether `filter ( Any element of the left (resp. right) branch must all be strictly inferior (resp. superior) to the label of the root. + +Remark it will remains _mostly_ an ordered binary tree. +Furthermore, by construction, each node value is unique in the tree. + +Here is our new version of `treeFromList`. We simply have replaced `filter` by `safefilter`. + +> treeFromList :: (Ord a, Show a) => [a] -> BinTree a +> treeFromList [] = Empty +> treeFromList (x:xs) = Node x left right +> where +> left = treeFromList $ safefilter ( right = treeFromList $ safefilter (>x) xs + +This new function `safefilter` is almost equivalent to `filter` but don't enter infinite loop if the result is a finite list. +If it cannot find an element for which the test is true after 10000 consecutive steps, then it considers to be the end of the search. + +> safefilter :: (a -> Bool) -> [a] -> [a] +> safefilter f l = safefilter' f l nbTry +> where +> nbTry = 10000 +> safefilter' _ _ 0 = [] +> safefilter' _ [] _ = [] +> safefilter' f (x:xs) n = +> if f x +> then x : safefilter' f xs nbTry +> else safefilter' f xs (n-1) + +Now run the program and be happy: + +> main = do +> putStrLn "take 10 shuffle" +> print $ take 10 shuffle +> putStrLn "\ntreeTakeDepth 8 (treeFromList shuffle)" +> print $ treeTakeDepth 8 (treeFromList $ shuffle) + +You should realize the time to print each value is different. +This is because Haskell compute each value when it needs it. +And in this case, this is when asked to print it on the screen. + +Impressively enough, try to replace the depth from `8` to `100`. +It will work without killing your RAM! +The flow and the memory management is done naturally by Haskell. + +Left as an exercise to the reader: + +- Even with large constant value for `deep` and `nbTry`, it seems to work nicely. But in the worst case, it can be exponential. + Create a worst case list to give as parameter to `treeFromList`. + _hint_: think about (`[0,-1,-1,....,-1,1,-1,...,-1,1,...]`). +- I first tried to implement `safefilter` as follow: +
    +  safefilter' f l = if filter f (take 10000 l) == []
    +                    then []
    +                    else filter f l
    +  
    + Explain why it doesn't work and can enter into an infinite loop. +- Suppose that `shuffle` is real random list with growing bounds. + If you study a bit this structure, you'll discover that with probability 1, + this structure is finite. + Using the following code + (suppose we could use `safefilter'` directly as if was not in the where of safefilter) + find a definition of `f` such that with probability `1`, + treeFromList' shuffle is infinite. And prove it. + Disclaimer, this is only a conjecture. + + +treeFromList' [] n = Empty +treeFromList' (x:xs) n = Node x left right + where + left = treeFromList' (safefilter' (x) xs (f n) + f = ??? + diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/02_Thanks/10_Thanks.lhs b/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/02_Thanks/10_Thanks.lhs new file mode 100644 index 0000000..16881cf --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/02_Thanks/10_Thanks.lhs @@ -0,0 +1,9 @@ +## Thanks + +Thanks to [`/r/haskell`](http://reddit.com/r/haskell) and +[`/r/programming`](http://reddit.com/r/programming). +Your comment were most than welcome. + +Particularly, I want to thank [Emm](https://github.com/Emm) a thousand times +for the time he spent on correcting my English. +Thank you man. diff --git a/Scratch/en/blog/Haskell-the-Hard-Way/index.html b/Scratch/en/blog/Haskell-the-Hard-Way/index.html new file mode 100644 index 0000000..17594c5 --- /dev/null +++ b/Scratch/en/blog/Haskell-the-Hard-Way/index.html @@ -0,0 +1,2359 @@ + + + + + + YBlog - Learn Haskell Fast and Hard + + + + + + + + + + + + + +
    + + +
    +

    Learn Haskell Fast and Hard

    +
    +
    +
    +
    +

    Magritte pleasure principle

    + +
    + +

    I really believe all developer should learn Haskell. I don’t think all should be super Haskell ninjas, but at least, they should discover what Haskell has to offer. Learning Haskell open your mind.

    +

    Mainstream languages share the same foundations:

    +
      +
    • variables
    • +
    • loops
    • +
    • pointers1
    • +
    • data structures, objects and classes (for most)
    • +
    +

    Haskell is very different. This language uses a lot of concepts I had never heard about before. Many of those concepts will help you become a better programmer.

    +

    But, learning Haskell can be hard. It was for me. In this article I try to provide what I lacked during my learning.

    +

    This article will certainly be hard to follow. This is on purpose. There is no shortcut to learning Haskell. It is hard and challenging. But I believe this is a good thing. It is because it is hard that Haskell is interesting.

    +

    The conventional method to learning Haskell is to read two books. First “Learn You a Haskell” and just after “Real World Haskell”. I also believe this is the right way to go. But, to learn what Haskell is all about, you’ll have to read them in detail.

    +

    On the other hand, this article is a very brief and dense overview of all major aspects of Haskell. I also added some informations I lacked while I learned Haskell.

    +

    The article contains five parts:

    +
      +
    • Introduction: a short example to show Haskell can be friendly.
    • +
    • Basic Haskell: Haskell syntax, and some essential notions.
    • +
    • Hard Difficulty Part: +
        +
      • Functional style; a progressive example, from imperative to functional style
      • +
      • Types; types and a standard binary tree example
      • +
      • Infinite Structure; manipulate an infinite binary tree!
      • +
    • +
    • Hell Difficulty Part: +
        +
      • Deal with IO; A very minimal example
      • +
      • IO trick explained; the hidden detail I lacked to understand IO
      • +
      • Monads; incredible how we can generalize
      • +
    • +
    • Appendix: +
        +
      • More on infinite tree; a more math oriented discussion about infinite trees
      • +
    • +
    +
    +Note: Each time you’ll see a separator with a filename ending in .lhs, you could click the filename to get this file. If you save the file as filename.lhs, you can run it with +
    +runhaskell filename.lhs
    +
    + +

    Some might not work, but most will. You should see a link just below.

    +
    +
    + +
    +

    01_basic/10_Introduction/00_hello_world.lhs

    +

    +Introduction +

    + +

    +Install +

    + +

    + +

    Tools:

    +
      +
    • ghc: Compiler similar to gcc for C.
    • +
    • ghci: Interactive Haskell (REPL)
    • +
    • runhaskell: Execute a program without compiling it. Convenient but very slow compared to compiled programs.
    • +
    +

    +Don’t be afraid +

    + +

    The Scream

    +

    Many book/articles about Haskell start by introducing some esoteric formula (quick sort, Fibonacci, etc…). I will do the exact opposite. At first I won’t show you any Haskell super power. I will start with similarities between Haskell and other programming languages. Let’s jump to the mandatory “Hello World”.

    +
    +
    main = putStrLn "Hello World!"
    +
    +

    To run it, you can save this code in a hello.hs and:

    +
    ~ runhaskell ./hello.hs
    +Hello World!
    +

    You could also download the literate Haskell source. You should see a link just above the introduction title. Download this file as 00_hello_world.lhs and:

    +
    ~ runhaskell 00_hello_world.lhs
    +Hello World!
    +

    01_basic/10_Introduction/00_hello_world.lhs

    +
    +

    01_basic/10_Introduction/10_hello_you.lhs

    +

    Now, a program asking your name and replying “Hello” using the name you entered:

    +
    +
    main = do
    +    print "What is your name?"
    +    name <- getLine
    +    print ("Hello " ++ name ++ "!")
    +
    +

    First, let us compare with a similar program in some imperative languages:

    +
    # Python
    +print "What is your name?"
    +name = raw_input()
    +print "Hello %s!" % name
    +
    # Ruby
    +puts "What is your name?"
    +name = gets.chomp
    +puts "Hello #{name}!"
    +
    // In C
    +#include <stdio.h>
    +int main (int argc, char **argv) {
    +    char name[666]; // <- An Evil Number!
    +    // What if my name is more than 665 character long?
    +    printf("What is your name?\n"); 
    +    scanf("%s", name);
    +    printf("Hello %s!\n", name);
    +    return 0;
    +}
    +

    The structure is the same, but there are some syntax differences. A major part of this tutorial will be dedicated to explaining why.

    +

    In Haskell, there is a main function and every object has a type. The type of main is IO (). This means, main will cause side effects.

    +

    Just remember that Haskell can look a lot like mainstream imperative languages.

    +

    01_basic/10_Introduction/10_hello_you.lhs

    +
    +

    01_basic/10_Introduction/20_very_basic.lhs

    +

    +Very basic Haskell +

    + +

    Picasso minimal owl

    +

    Before continuing you need to be warned about some essential properties of Haskell.

    +

    Functional

    +

    Haskell is a functional language. If you have an imperative language background, you’ll have to learn a lot of new things. Hopefully many of these new concepts will help you to program even in imperative languages.

    +

    Smart Static Typing

    +

    Instead of being in your way like in C, C++ or Java, the type system is here to help you.

    +

    Purity

    +

    Generally your functions won’t modify anything in the outside world. This means, it can’t modify the value of a variable, can’t get user input, can’t write on the screen, can’t launch a missile. On the other hand, parallelism will be very easy to achieve. Haskell makes it clear where effects occur and where you are pure. Also, it will be far easier to reason about your program. Most bugs will be prevented in the pure parts of your program.

    +

    Furthermore pure functions follow a fundamental law in Haskell:

    +
    +

    Applying a function with the same parameters always returns the same value.

    +
    +

    Laziness

    +

    Laziness by default is a very uncommon language design. By default, Haskell evaluates something only when it is needed. In consequence, it provides a very elegant way to manipulate infinite structures for example.

    +

    A last warning on how you should read Haskell code. For me, it is like reading scientific papers. Some parts are very clear, but when you see a formula, just focus and read slower. Also, while learning Haskell, it really doesn’t matter much if you don’t understand syntax details. If you meet a >>=, <$>, <- or any other weird symbol, just ignore them and follows the flow of the code.

    +

    +Function declaration +

    + +

    You might be used to declare functions like this:

    +

    In C:

    +
    int f(int x, int y) {
    +    return x*x + y*y;
    +}
    +

    In Javascript:

    +
    function f(x,y) {
    +    return x*x + y*y;
    +}
    +

    in Python:

    +
    def f(x,y):
    +    return x*x + y*y
    +

    in Ruby:

    +
    def f(x,y)
    +    x*x + y*y
    +end
    +

    In Scheme:

    +
    (define (f x y)
    +    (+ (* x x) (* y y)))
    +

    Finally, the Haskell way is:

    +
    f x y = x*x + y*y
    +

    Very clean. No parenthesis, no def.

    +

    Don’t forget, Haskell uses functions and types a lot. It is thus very easy to define them. The syntax was particularly well thought for these objects.

    +

    +A Type Example +

    + +

    The usual way is to declare the type of your function. This is not mandatory. The compiler is smart enough to discover it for you.

    +

    Let’s play a little.

    +
    +
    -- We declare the type using ::
    +f :: Int -> Int -> Int
    +f x y = x*x + y*y
    +
    +main = print (f 2 3)
    +
    +
    ~ runhaskell 20_very_basic.lhs
    +13
    +

    01_basic/10_Introduction/20_very_basic.lhs

    +
    +

    01_basic/10_Introduction/21_very_basic.lhs

    +

    Now try

    +
    +
    f :: Int -> Int -> Int
    +f x y = x*x + y*y
    +
    +main = print (f 2.3 4.2)
    +
    +

    You get this error:

    +
    21_very_basic.lhs:6:23:
    +    No instance for (Fractional Int)
    +      arising from the literal `4.2'
    +    Possible fix: add an instance declaration for (Fractional Int)
    +    In the second argument of `f', namely `4.2'
    +    In the first argument of `print', namely `(f 2.3 4.2)'
    +    In the expression: print (f 2.3 4.2)
    +

    The problem: 4.2 isn’t an Int.

    +

    01_basic/10_Introduction/21_very_basic.lhs

    +
    +

    01_basic/10_Introduction/22_very_basic.lhs

    +

    The solution, don’t declare the type for f. Haskell will infer the most general type for us:

    +
    +
    f x y = x*x + y*y
    +
    +main = print (f 2.3 4.2)
    +
    +

    It works! Great, we don’t have to declare a new function for every single type. For example, in C, you’ll have to declare a function for int, for float, for long, for double, etc…

    +

    But, what type should we declare? To discover the type Haskell has found for us, just launch ghci:

    +
    
    +% ghci
    +GHCi, version 7.0.4: http://www.haskell.org/ghc/  :? for help
    +Loading package ghc-prim ... linking ... done.
    +Loading package integer-gmp ... linking ... done.
    +Loading package base ... linking ... done.
    +Loading package ffi-1.0 ... linking ... done.
    +Prelude> let f x y = x*x + y*y
    +Prelude> :type f
    +f :: Num a => a -> a -> a
    +
    + +

    Uh? What is this strange type?

    +
    Num a => a -> a -> a
    +

    First, let’s focus on the right part a -> a -> a. To understand it, just look at a list of progressive examples:

    +

    The written type | Its meaning |
    Int | the type Int |
    Int -> Int | the type function from Int to Int |
    Float -> Int | the type function from Float to Int |
    a -> Int | the type function from any type to Int |
    a -> a | the type function from any type a to the same type a |
    a -> a -> a | the type function of two arguments of any type a to the same type a |

    +

    In the type a -> a -> a, the letter a is a type variable. It means f is a function with two arguments and both arguments and the result have the same type. The type variable a could take many different type value. For example Int, Integer, Float

    +

    So instead of having a forced type like in C with declaring the function for int, long, float, double, etc… We declare only one function like in a dynamically typed language.

    +

    Generally a can be any type. For example a String, an Int, but also more complex types, like Trees, other functions, etc… But here our type is prefixed with Num a =>.

    +

    Num is a type class. A type class can be understood as a set of types. Num contains only types which behave like numbers. More precisely, Num is class containing types who implement a specific list of functions, and in particular (+) and (*).

    +

    Type classes are a very powerful language construct. We can do some incredibly powerful stuff with this. More on this later.

    +

    Finally, Num a => a -> a -> a means:

    +

    Let a be a type belonging to the Num type class. This is a function from type a to (a -> a).

    +

    Yes, strange. In fact, in Haskell no function really has two arguments. Instead all functions have only one argument. But we will note that taking two arguments is equivalent to taking one argument and returning a function taking the second argument as parameter.

    +

    More precisely f 3 4 is equivalent to (f 3) 4. Note f 3 is a function:

    +
    f :: Num a :: a -> a -> a
    +
    +g :: Num a :: a -> a
    +g = f 3
    +
    +g y ⇔ 3*3 + y*y
    +

    Another notation exists for functions. The lambda notation allows us to create functions without assigning them a name. We call them anonymous function. We could have written:

    +
    g = \y -> 3*3 + y*y
    +

    The \ is used because it looks like λ and is ASCII.

    +

    If you are not used to functional programming your brain should start to heat up. It is time to make a real application.

    +

    01_basic/10_Introduction/22_very_basic.lhs

    +
    +

    01_basic/10_Introduction/23_very_basic.lhs

    +

    But just before that, we should verify the type system works as expected:

    +
    +
    f :: Num a => a -> a -> a
    +f x y = x*x + y*y
    +
    +main = print (f 3 2.4)
    +
    +

    It works, because, 3 is a valid representation both for Fractional numbers like Float and for Integer. As 2.4 is a Fractional number, 3 is then interpreted as being also a Fractional number.

    +

    01_basic/10_Introduction/23_very_basic.lhs

    +
    +

    01_basic/10_Introduction/24_very_basic.lhs

    +

    If we force our function to work with different types, it will fail:

    +
    +
    f :: Num a => a -> a -> a
    +f x y = x*x + y*y
    +
    +x :: Int
    +x = 3
    +y :: Float
    +y = 2.4
    +main = print (f x y) -- won't work because type x ≠ type y
    +
    +

    The compiler complains. The two parameters must have the same type.

    +

    If you believe it is a bad idea, and the compiler should make the transformation from a type to another for you, you should really watch this great (and funny) video: WAT

    +

    01_basic/10_Introduction/24_very_basic.lhs

    +

    +Essential Haskell +

    + +

    Kandinsky Gugg

    +

    I suggest you to skim this part. Think of it like a reference. Haskell has a lot of features. Many informations are missing here. Get back here if notation feels strange.

    +

    I use the symbol to state that two expression are equivalent. It is a meta notation, does not exists in Haskell. I will also use to show what is the return of an expression.

    +

    +Notations +

    + +
    +Arithmetic +
    + +
    3 + 2 * 6 / 3 ⇔ 3 + ((2*6)/3)
    +
    +Logic +
    + +
    True || False ⇒ True
    +True && False ⇒ False
    +True == False ⇒ False
    +True /= False ⇒ True  (/=) is the operator for different
    +
    +Powers +
    + +
    x^n     for n an integral (understand Int or Integer)
    +x**y    for y any kind of number (Float for example)
    +

    Integer have no limit except the capacity of your machine:

    +
    4^103
    +102844034832575377634685573909834406561420991602098741459288064
    +

    Yeah! And also rational numbers FTW! But you need to import the module Data.Ratio:

    +
    $ ghci
    +....
    +Prelude> :m Data.Ratio
    +Data.Ratio> (11 % 15) * (5 % 3)
    +11 % 9
    +
    +Lists +
    + +
    []                      ⇔ empty list
    +[1,2,3]                 ⇔ List of integral
    +["foo","bar","baz"]     ⇔ List of String
    +1:[2,3]                 ⇔ [1,2,3], (:) prepend one element
    +1:2:[]                  ⇔ [1,2]
    +[1,2] ++ [3,4]          ⇔ [1,2,3,4], (++) concatenate
    +[1,2,3] ++ ["foo"]      ⇔ ERROR String ≠ Integral
    +[1..4]                  ⇔ [1,2,3,4]
    +[1,3..10]               ⇔ [1,3,5,7,9]
    +[2,3,5,7,11..100]       ⇔ ERROR! I am not so smart!
    +[10,9..1]               ⇔ [10,9,8,7,6,5,4,3,2,1]
    +
    +Strings +
    + +

    In Haskell strings are list of Char.

    +
    'a' :: Char
    +"a" :: [Char]
    +""  ⇔ []
    +"ab" ⇔ ['a','b'] ⇔  'a':"b" ⇔ 'a':['b'] ⇔ 'a':'b':[]
    +"abc" ⇔ "ab"++"c"
    +
    +

    Remark: In real code you shouldn’t use list of char to represent text. You should mostly use Data.Text instead. If you want to represent stream of ASCII char, you should use Data.ByteString.

    +
    +
    +Tuples +
    + +

    The type of couple is (a,b). Elements in a tuple can have different type.

    +
    -- All these tuple are valid
    +(2,"foo")
    +(3,'a',[2,3])
    +((2,"a"),"c",3)
    +
    +fst (x,y)       ⇒  x
    +snd (x,y)       ⇒  y
    +
    +fst (x,y,z)     ⇒  ERROR: fst :: (a,b) -> a
    +snd (x,y,z)     ⇒  ERROR: snd :: (a,b) -> b
    +
    +Deal with parentheses +
    + +

    To remove some parentheses you can use two functions: ($) and (.).

    +
    -- By default:
    +f g h x         ⇔  (((f g) h) x)
    +
    +-- the $ replace parenthesis from the $
    +-- to the end of the expression
    +f g $ h x       ⇔  f g (h x) ⇔ (f g) (h x)
    +f $ g h x       ⇔  f (g h x) ⇔ f ((g h) x)
    +f $ g $ h x     ⇔  f (g (h x))
    +
    +-- (.) the composition function
    +(f . g) x       ⇔  f (g x)
    +(f . g . h) x   ⇔  f (g (h x))
    +
    +

    01_basic/20_Essential_Haskell/10a_Functions.lhs

    +

    +Useful notations for functions +

    + +

    Just a reminder:

    +
    x :: Int            ⇔ x is of type Int
    +x :: a              ⇔ x can be of any type
    +x :: Num a => a     ⇔ x can be any type a
    +                      such that a belongs to Num type class 
    +f :: a -> b         ⇔ f is a function from a to b
    +f :: a -> b -> c    ⇔ f is a function from a to (b→c)
    +f :: (a -> b) -> c  ⇔ f is a function from (a→b) to c
    +

    Defining the type of a function before its declaration isn’t mandatory. Haskell infers the most general type for you. But it is considered a good practice to do so.

    +

    Infix notation

    +
    +
    square :: Num a => a -> a  
    +square x = x^2
    +
    +

    Note ^ use infix notation. For each infix operator there its associated prefix notation. You just have to put it inside parenthesis.

    +
    +
    square' x = (^) x 2
    +
    +square'' x = (^2) x
    +
    +

    We can remove x in the left and right side! It’s called η-reduction.

    +
    +
    square''' = (^2)
    +
    +

    Note we can declare function with ' in their name. Here:

    +
    +

    squaresquare'square''square '''

    +
    +

    Tests

    +

    An implementation of the absolute function.

    +
    +
    absolute :: (Ord a, Num a) => a -> a
    +absolute x = if x >= 0 then x else -x
    +
    +

    Note: the if .. then .. else Haskell notation is more like the ¤?¤:¤ C operator. You cannot forget the else.

    +

    Another equivalent version:

    +
    +
    absolute' x
    +    | x >= 0 = x
    +    | otherwise = -x
    +
    + +
    +

    Notation warning: indentation is important in Haskell. Like in Python, a bad indentation could break your code!

    +
    +
    + +
    +
    main = do
    +      print $ square 10
    +      print $ square' 10
    +      print $ square'' 10
    +      print $ square''' 10
    +      print $ absolute 10
    +      print $ absolute (-10)
    +      print $ absolute' 10
    +      print $ absolute' (-10)
    +
    +
    + +

    01_basic/20_Essential_Haskell/10a_Functions.lhs

    +

    +Hard Part +

    + +

    The hard part can now begin.

    +

    +Functional style +

    + +

    Biomechanical Landscape by H.R. Giger

    +

    In this section, I will give a short example of the impressive refactoring ability provided by Haskell. We will select a problem and solve it using a standard imperative way. Then I will make the code evolve. The end result will be both more elegant and easier to adapt.

    +

    Let’s solve the following problem:

    +
    +

    Given a list of integers, return the sum of the even numbers in the list.

    +

    example: [1,2,3,4,5] ⇒ 2 + 4 ⇒ 6

    +
    +

    To show differences between the functional and imperative approach, I’ll start by providing an imperative solution (in Javascript):

    +
    function evenSum(list) {
    +    var result = 0;
    +    for (var i=0; i< list.length ; i++) {
    +        if (list[i] % 2 ==0) {
    +            result += list[i];
    +        }
    +    }
    +    return result;
    +}
    +

    But, in Haskell we don’t have variables, nor for loop. One solution to achieve the same result without loops is to use recursion.

    +
    +

    Remark: Recursion is generally perceived as slow in imperative languages. But it is generally not the case in functional programming. Most of the time Haskell will handle recursive functions efficiently.

    +
    +

    Here is a C version of the recursive function. Note that for simplicity, I assume the int list ends with the first 0 value.

    +
    int evenSum(int *list) {
    +    return accumSum(0,list);
    +}
    +
    +int accumSum(int n, int *list) {
    +    int x;
    +    int *xs;
    +    if (*list == 0) { // if the list is empty
    +        return n;
    +    } else {
    +        x = list[0]; // let x be the first element of the list
    +        xs = list+1; // let xs be the list without x
    +        if ( 0 == (x%2) ) { // if x is even
    +            return accumSum(n+x, xs);
    +        } else {
    +            return accumSum(n, xs);
    +        }
    +    }
    +}
    +

    Keep this code in mind. We will translate it into Haskell. But before, I need to introduce three simple but useful functions we will use:

    +
    even :: Integral a => a -> Bool
    +head :: [a] -> a
    +tail :: [a] -> [a]
    +

    even verifies if a number is even.

    +
    even :: Integral a => a -> Bool
    +even 3   False
    +even 2   True
    +

    head returns the first element of a list:

    +
    head :: [a] -> a
    +head [1,2,3]  1
    +head []       ERROR
    +

    tail returns all elements of a list, except the first:

    +
    tail :: [a] -> [a]
    +tail [1,2,3]  [2,3]
    +tail [3]      []
    +tail []       ERROR
    +

    Note that for any non empty list l, l ⇔ (head l):(tail l)

    +
    +

    02_Hard_Part/11_Functions.lhs

    +

    The first Haskell solution. The function evenSum returns the sum of all even numbers in a list:

    +
    +
    -- Version 1
    +evenSum :: [Integer] -> Integer
    +
    +evenSum l = accumSum 0 l
    +
    +accumSum n l = if l == []
    +                  then n
    +                  else let x = head l 
    +                           xs = tail l 
    +                       in if even x
    +                              then accumSum (n+x) xs
    +                              else accumSum n xs
    +
    +

    To test a function you can use ghci:

    +
    +% ghci
    +GHCi, version 7.0.3: http://www.haskell.org/ghc/  :? for help
    +Loading package ghc-prim ... linking ... done.
    +Loading package integer-gmp ... linking ... done.
    +Loading package base ... linking ... done.
    +Prelude> :load 11_Functions.lhs 
    +[1 of 1] Compiling Main             ( 11_Functions.lhs, interpreted )
    +Ok, modules loaded: Main.
    +*Main> evenSum [1..5]
    +6
    +
    + +

    Here is an example of execution2:

    +
    +*Main> evenSum [1..5]
    +accumSum 0 [1,2,3,4,5]
    +1 is odd
    +accumSum 0 [2,3,4,5]
    +2 is even
    +accumSum (0+2) [3,4,5]
    +3 is odd
    +accumSum (0+2) [4,5]
    +4 is even
    +accumSum (0+2+4) [5]
    +5 is odd
    +accumSum (0+2+4) []
    +l == []
    +0+2+4
    +0+6
    +6
    +
    + +

    Coming from an imperative language all should seem right. In reality many things can be improved. First, we can generalize the type.

    +
    evenSum :: Integral a => [a] -> a
    +
    + +
    +
    main = do print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/11_Functions.lhs

    +
    +

    02_Hard_Part/12_Functions.lhs

    +

    Next, we can use sub functions using where or let. This way our accumSum function won’t pollute the global namespace.

    +
    +
    -- Version 2
    +evenSum :: Integral a => [a] -> a
    +
    +evenSum l = accumSum 0 l
    +    where accumSum n l = 
    +            if l == []
    +                then n
    +                else let x = head l 
    +                         xs = tail l 
    +                     in if even x
    +                            then accumSum (n+x) xs
    +                            else accumSum n xs
    +
    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/12_Functions.lhs

    +
    +

    02_Hard_Part/13_Functions.lhs

    +

    Next, we can use pattern matching.

    +
    +
    -- Version 3
    +evenSum l = accumSum 0 l
    +    where 
    +        accumSum n [] = n
    +        accumSum n (x:xs) = 
    +             if even x
    +                then accumSum (n+x) xs
    +                else accumSum n xs
    +
    +

    What is pattern matching? Use values instead of general parameter names3.

    +

    Instead of saying: foo l = if l == [] then <x> else <y> You simply state:

    +
    foo [] =  <x>
    +foo l  =  <y>
    +

    But pattern matching goes even further. It is also able to inspect the inner data of a complex value. We can replace

    +
    foo l =  let x  = head l 
    +             xs = tail l
    +         in if even x 
    +             then foo (n+x) xs
    +             else foo n xs
    +

    with

    +
    foo (x:xs) = if even x 
    +                 then foo (n+x) xs
    +                 else foo n xs
    +

    This is a very useful feature. It makes our code both terser and easier to read.

    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/13_Functions.lhs

    +
    +

    02_Hard_Part/14_Functions.lhs

    +

    In Haskell you can simplify function definition by η-reducing them. For example, instead of writing:

    +
    f x = (some expresion) x
    +

    you can simply write

    +
    f = some expression
    +

    We use this method to remove the l:

    +
    +
    -- Version 4
    +evenSum :: Integral a => [a] -> a
    +
    +evenSum = accumSum 0
    +    where 
    +        accumSum n [] = n
    +        accumSum n (x:xs) = 
    +             if even x
    +                then accumSum (n+x) xs
    +                else accumSum n xs
    +
    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/14_Functions.lhs

    +
    +

    02_Hard_Part/15_Functions.lhs

    +

    +Higher Order Functions +

    + +

    Escher

    +

    To make things even better we should use higher order functions. What are these beasts? Higher order functions are functions taking functions as parameter.

    +

    Here are some examples:

    +
    filter :: (a -> Bool) -> [a] -> [a]
    +map :: (a -> b) -> [a] -> [b]
    +foldl :: (a -> b -> a) -> a -> [b] -> a
    +

    Let’s proceed by small steps.

    +
    -- Version 5
    +evenSum l = mysum 0 (filter even l)
    +    where
    +      mysum n [] = n
    +      mysum n (x:xs) = mysum (n+x) xs
    +

    where

    +
    filter even [1..10] ⇔  [2,4,6,8,10]
    +

    The function filter takes a function of type (a -> Bool) and a list of type [a]. It returns a list containing only elements for which the function returned true.

    +

    Our next step is to use another way to simulate a loop. We will use the foldl function to accumulate a value. The function foldl captures a general coding pattern:

    +
    +myfunc list = foo initialValue list
    +    foo accumulated []     = accumulated
    +    foo tmpValue    (x:xs) = foo (bar tmpValue x) xs
    +
    + +

    Which can be replaced by:

    +
    +myfunc list = foldl bar initialValue list
    +
    + +

    If you really want to know how the magic works. Here is the definition of foldl.

    +
    foldl f z [] = z
    +foldl f z (x:xs) = foldl f (f z x) xs
    +
    foldl f z [x1,...xn]
    +⇔  f (... (f (f z x1) x2) ...) xn
    +

    But as Haskell is lazy, it doesn’t evaluate (f z x) and pushes it to the stack. This is why we generally use foldl' instead of foldl; foldl' is a strict version of foldl. If you don’t understand what lazy and strict means, don’t worry, just follow the code as if foldl and foldl' where identical.

    +

    Now our new version of evenSum becomes:

    +
    -- Version 6
    +-- foldl' isn't accessible by default
    +-- we need to import it from the module Data.List
    +import Data.List
    +evenSum l = foldl' mysum 0 (filter even l)
    +  where mysum acc value = acc + value
    +

    Version we can simplify by using directly a lambda notation. This way we don’t have to create the temporary name mysum.

    +
    +
    -- Version 7
    +-- Generally it is considered a good practice
    +-- to import only the necessary function(s)
    +import Data.List (foldl')
    +evenSum l = foldl' (\x y -> x+y) 0 (filter even l)
    +
    +

    And of course, we note that

    +
    (\x y -> x+y) ⇔ (+)
    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/15_Functions.lhs

    +
    +

    02_Hard_Part/16_Functions.lhs

    +

    Finally

    +
    -- Version 8
    +import Data.List (foldl')
    +evenSum :: Integral a => [a] -> a
    +evenSum l = foldl' (+) 0 (filter even l)
    +

    foldl' isn’t the easiest function to intuit. If you are not used to it, you should study it a bit.

    +

    To help you understand what’s going on here, a step by step evaluation:

    +
    +  evenSum [1,2,3,4]
    +⇒ foldl' (+) 0 (filter even [1,2,3,4])
    +⇒ foldl' (+) 0 [2,4]
    +⇒ foldl' (+) (0+2) [4] 
    +⇒ foldl' (+) 2 [4]
    +⇒ foldl' (+) (2+4) []
    +⇒ foldl' (+) 6 []
    +⇒ 6
    +
    + +

    Another useful higher order function is (.). The (.) function corresponds to the mathematical composition.

    +
    (f . g . h) x ⇔  f ( g (h x))
    +

    We can take advantage of this operator to η-reduce our function:

    +
    -- Version 9
    +import Data.List (foldl')
    +evenSum :: Integral a => [a] -> a
    +evenSum = (foldl' (+) 0) . (filter even)
    +

    Also, we could rename some parts to make it clearer:

    +
    +
    -- Version 10 
    +import Data.List (foldl')
    +sum' :: (Num a) => [a] -> a
    +sum' = foldl' (+) 0
    +evenSum :: Integral a => [a] -> a
    +evenSum = sum' . (filter even)
    +
    +

    It is time to discuss a bit. What did we gain by using higher order functions?

    +

    At first, you can say it is terseness. But in fact, it has more to do with better thinking. Suppose we want to modify slightly our function. We want to get the sum of all even square of element of the list.

    +
    [1,2,3,4] ▷ [1,4,9,16] ▷ [4,16] ▷ 20
    +

    Update the version 10 is extremely easy:

    +
    +
    squareEvenSum = sum' . (filter even) . (map (^2))
    +squareEvenSum' = evenSum . (map (^2))
    +squareEvenSum'' = sum' . (map (^2)) . (filter even)
    +
    +

    We just had to add another “transformation function”4.

    +
    map (^2) [1,2,3,4] ⇔ [1,4,9,16]
    +

    The map function simply apply a function to all element of a list.

    +

    We didn’t had to modify anything inside the function definition. It feels more modular. But in addition you can think more mathematically about your function. You can then use your function as any other one. You can compose, map, fold, filter using your new function.

    +

    To modify version 1 is left as an exercise to the reader ☺.

    +

    If you believe we reached the end of generalization, then know you are very wrong. For example, there is a way to not only use this function on lists but on any recursive type. If you want to know how, I suggest you to read this quite fun article: Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire by Meijer, Fokkinga and Paterson.

    +

    This example should show you how great pure functional programming is. Unfortunately, using pure functional programming isn’t well suited to all usages. Or at least such a language hasn’t been found yet.

    +

    One of the great powers of Haskell is the ability to create DSLs (Domain Specific Language) making it easy to change the programming paradigm.

    +

    In fact, Haskell is also great when you want to write imperative style programming. Understanding this was really hard for me when learning Haskell. A lot of effort has been done to explain to you how much functional approach is superior. Then when you start the imperative style of Haskell, it is hard to understand why and how.

    +

    But before talking about this Haskell super-power, we must talk about another essential aspect of Haskell: Types.

    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/16_Functions.lhs

    +

    +Types +

    + +

    Dali, the madonna of port Lligat

    +
    +

    tl;dr:

    +
      +
    • type Name = AnotherType is just an alias and the compiler doesn’t do any difference between Name and AnotherType.
    • +
    • data Name = NameConstructor AnotherType make a difference.
    • +
    • data can construct structures which can be recursives.
    • +
    • deriving is magic and create functions for you.
    • +
    +
    +

    In Haskell, types are strong and static.

    +

    Why is this important? It will help you greatly to avoid mistakes. In Haskell, most bugs are caught during the compilation of your program. And the main reason is because of the type inference during compilation. It will be easy to detect where you used the wrong parameter at the wrong place for example.

    +

    +Type inference +

    + +

    Static typing is generally essential to reach fast execution time. But most statically typed languages are bad at generalizing concepts. Haskell’s saving grace is that it can infer types.

    +

    Here is a simple example. The square function in Haskell:

    +
    square x = x * x
    +

    This function can square any Numeral type. You can provide square with an Int, an Integer, a Float a Fractional and even Complex. Proof by example:

    +
    % ghci
    +GHCi, version 7.0.4:
    +...
    +Prelude> let square x = x*x
    +Prelude> square 2
    +4
    +Prelude> square 2.1
    +4.41
    +Prelude> -- load the Data.Complex module
    +Prelude> :m Data.Complex
    +Prelude Data.Complex> square (2 :+ 1)
    +3.0 :+ 4.0
    +

    x :+ y is the notation for the complex (x + ib).

    +

    Now compare with the amount of code necessary in C:

    +
    int     int_square(int x) { return x*x; }
    +
    +float   float_square(float x) {return x*x; }
    +
    +complex complex_square (complex z) {
    +    complex tmp;
    +    tmp.real = z.real * z.real - z.img * z.img;
    +    tmp.img = 2 * z.img * z.real;
    +}
    +
    +complex x,y;
    +y = complex_square(x);
    +

    For each type, you need to write a new function. The only way to work around this problem is to use some meta-programming trick. For example using the pre-processor. In C++ there is a better way, the C++ templates:

    +

    ~~~~~~ {.c++} #include #include using namespace std;

    +

    template T square(T x) { return x*x; }

    +

    int main() { // int int sqr_of_five = square(5); cout << sqr_of_five << endl; // double cout << (double)square(5.3) << endl; // complex cout << square( complex(5,3) ) << endl; return 0; } ~~~~~~

    +

    C++ does a far better job than C. For more complex function the syntax can be hard to follow: look at this article for example.

    +

    In C++ you must declare that a function can work with different types. In Haskell this is the opposite. The function will be as general as possible by default.

    +

    Type inference gives Haskell the feeling of freedom that dynamically typed languages provide. But unlike dynamically typed languages, most errors are caught before the execution. Generally, in Haskell:

    +
    +

    “if it compiles it certainly does what you intended”

    +
    +
    +

    02_Hard_Part/21_Types.lhs

    +

    +Type construction +

    + +

    You can construct your own types. First you can use aliases or type synonyms.

    +
    +
    type Name   = String
    +type Color  = String
    +
    +showInfos :: Name ->  Color -> String
    +showInfos name color =  "Name: " ++ name
    +                        ++ ", Color: " ++ color
    +name :: Name
    +name = "Robin"
    +color :: Color
    +color = "Blue"
    +main = putStrLn $ showInfos name color
    +
    +

    02_Hard_Part/21_Types.lhs

    +
    +

    02_Hard_Part/22_Types.lhs

    +

    But it doesn’t protect you much. Try to swap the two parameter of showInfos and run the program:

    +
        putStrLn $ showInfos color name
    +

    It will compile and execute. In fact you can replace Name, Color and String everywhere. The compiler will treat them as completely identical.

    +

    Another method is to create your own types using the keyword data.

    +
    +
    data Name   = NameConstr String
    +data Color  = ColorConstr String
    +
    +showInfos :: Name ->  Color -> String
    +showInfos (NameConstr name) (ColorConstr color) =
    +      "Name: " ++ name ++ ", Color: " ++ color
    +
    +name  = NameConstr "Robin"
    +color = ColorConstr "Blue"
    +main = putStrLn $ showInfos name color
    +
    +

    Now if you switch parameters of showInfos, the compiler complains! A possible mistake you could never do again. The only price is to be more verbose.

    +

    Also remark constructor are functions:

    +
    NameConstr  :: String -> Name
    +ColorConstr :: String -> Color
    +

    The syntax of data is mainly:

    +
    data TypeName =   ConstructorName  [types]
    +                | ConstructorName2 [types]
    +                | ...
    +

    Generally the usage is to use the same name for the DataTypeName and DataTypeConstructor.

    +

    Example:

    +
    data Complex = Num a => Complex a a
    +

    Also you can use the record syntax:

    +
    data DataTypeName = DataConstructor {
    +                      field1 :: [type of field1]
    +                    , field2 :: [type of field2]
    +                    ...
    +                    , fieldn :: [type of fieldn] }
    +

    And many accessors are made for you. Furthermore you can use another order when setting values.

    +

    Example:

    +
    data Complex = Num a => Complex { real :: a, img :: a}
    +c = Complex 1.0 2.0
    +z = Complex { real = 3, img = 4 }
    +real c  1.0
    +img z  4
    +

    02_Hard_Part/22_Types.lhs

    +
    +

    02_Hard_Part/23_Types.lhs

    +

    +Recursive type +

    + +

    You already encountered a recursive type: lists. You can re-create lists, but with a more verbose syntax:

    +
    data List a = Empty | Cons a (List a)
    +

    If you really want to use an easier syntax you can use an infix name for constructors.

    +
    infixr 5 :::
    +data List a = Nil | a ::: (List a)
    +

    The number after infixr is the priority.

    +

    If you want to be able to print (Show), read (Read), test equality (Eq) and compare (Ord) your new data structure you can tell Haskell to derive the appropriate functions for you.

    +
    +
    infixr 5 :::
    +data List a = Nil | a ::: (List a) 
    +              deriving (Show,Read,Eq,Ord)
    +
    +

    When you add deriving (Show) to your data declaration, Haskell create a show function for you. We’ll see soon how you can use your own show function.

    +
    +
    convertList [] = Nil
    +convertList (x:xs) = x ::: convertList xs
    +
    +
    +
    main = do
    +      print (0 ::: 1 ::: Nil)
    +      print (convertList [0,1])
    +
    +

    This prints:

    +
    0 ::: (1 ::: Nil)
    +0 ::: (1 ::: Nil)
    +

    02_Hard_Part/23_Types.lhs

    +
    +

    02_Hard_Part/30_Trees.lhs

    +

    +Trees +

    + +

    Magritte, l

    +

    We’ll just give another standard example: binary trees.

    +
    +
    import Data.List
    +
    +data BinTree a = Empty
    +                 | Node a (BinTree a) (BinTree a)
    +                              deriving (Show)
    +
    +

    We will also create a function which turns a list into an ordered binary tree.

    +
    +
    treeFromList :: (Ord a) => [a] -> BinTree a
    +treeFromList [] = Empty
    +treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
    +                             (treeFromList (filter (>x) xs))
    +
    +

    Look at how elegant this function is. In plain English:

    +
      +
    • an empty list will be converted to an empty tree.
    • +
    • a list (x:xs) will be converted to a tree where:
    • +
    • The root is x
    • +
    • Its left subtree is the tree created from members of the list xs which are strictly inferior to x and
    • +
    • the right subtree is the tree created from members of the list xs which are strictly superior to x.
    • +
    +
    +
    main = print $ treeFromList [7,2,4,8]
    +
    +

    You should obtain the following:

    +
    Node 7 (Node 2 Empty (Node 4 Empty Empty)) (Node 8 Empty Empty)
    +

    This is an informative but quite unpleasant representation of our tree.

    +

    02_Hard_Part/30_Trees.lhs

    +
    +

    02_Hard_Part/31_Trees.lhs

    +

    Just for fun, let’s code a better display for our trees. I simply had fun making a nice function to display trees in a general way. You can safely skip this part if you find it too difficult to follow.

    +

    We have a few changes to make. We remove the deriving (Show) from the declaration of our BinTree type. And it might also be useful to make our BinTree an instance of (Eq and Ord). We will be able to test equality and compare trees.

    +
    +
    data BinTree a = Empty
    +                 | Node a (BinTree a) (BinTree a)
    +                  deriving (Eq,Ord)
    +
    +

    Without the deriving (Show), Haskell doesn’t create a show method for us. We will create our own version of show. To achieve this, we must declare that our newly created type BinTree a is an instance of the type class Show. The general syntax is:

    +
    instance Show (BinTree a) where
    +   show t = ... -- You declare your function here
    +

    Here is my version of how to show a binary tree. Don’t worry about the apparent complexity. I made a lot of improvements in order to display even stranger objects.

    +
    +
    -- declare BinTree a to be an instance of Show
    +instance (Show a) => Show (BinTree a) where
    +  -- will start by a '<' before the root
    +  -- and put a : a begining of line
    +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
    +    where
    +    -- treeshow pref Tree
    +    --   shows a tree and starts each line with pref
    +    -- We don't display the Empty tree
    +    treeshow pref Empty = ""
    +    -- Leaf
    +    treeshow pref (Node x Empty Empty) =
    +                  (pshow pref x)
    +
    +    -- Right branch is empty
    +    treeshow pref (Node x left Empty) =
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " left)
    +
    +    -- Left branch is empty
    +    treeshow pref (Node x Empty right) =
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- Tree with left and right children non empty
    +    treeshow pref (Node x left right) =
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "|--" "|  " left) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- shows a tree using some prefixes to make it nice
    +    showSon pref before next t =
    +                  pref ++ before ++ treeshow (pref ++ next) t
    +
    +    -- pshow replaces "\n" by "\n"++pref
    +    pshow pref x = replace '\n' ("\n"++pref) (show x)
    +
    +    -- replaces one char by another string
    +    replace c new string =
    +      concatMap (change c new) string
    +      where
    +          change c new x
    +              | x == c = new
    +              | otherwise = x:[] -- "x"
    +
    +

    The treeFromList method remains identical.

    +
    +
    treeFromList :: (Ord a) => [a] -> BinTree a
    +treeFromList [] = Empty
    +treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
    +                             (treeFromList (filter (>x) xs))
    +
    +

    And now, we can play:

    +
    +
    main = do
    +  putStrLn "Int binary tree:"
    +  print $ treeFromList [7,2,4,8,1,3,6,21,12,23]
    +
    +
    Int binary tree:
    +< 7
    +: |--2
    +: |  |--1
    +: |  `--4
    +: |     |--3
    +: |     `--6
    +: `--8
    +:    `--21
    +:       |--12
    +:       `--23
    +

    Now it is far better! The root is shown by starting the line with the < character. And each following line starts with a :. But we could also use another type.

    +
    +
      putStrLn "\nString binary tree:"
    +  print $ treeFromList ["foo","bar","baz","gor","yog"]
    +
    +
    String binary tree:
    +< "foo"
    +: |--"bar"
    +: |  `--"baz"
    +: `--"gor"
    +:    `--"yog"
    +

    As we can test equality and order trees, we can make tree of trees!

    +
    +
      putStrLn "\nBinary tree of Char binary trees:"
    +  print ( treeFromList
    +           (map treeFromList ["baz","zara","bar"]))
    +
    +
    Binary tree of Char binary trees:
    +< < 'b'
    +: : |--'a'
    +: : `--'z'
    +: |--< 'b'
    +: |  : |--'a'
    +: |  : `--'r'
    +: `--< 'z'
    +:    : `--'a'
    +:    :    `--'r'
    +

    This is why I chose to prefix each line of tree display by : (except for the root).

    +

    Yo Dawg Tree

    +
    +
      putStrLn "\nTree of Binary trees of Char binary trees:"
    +  print $ (treeFromList . map (treeFromList . map treeFromList))
    +             [ ["YO","DAWG"]
    +             , ["I","HEARD"]
    +             , ["I","HEARD"]
    +             , ["YOU","LIKE","TREES"] ]
    +
    +

    Which is equivalent to

    +
    print ( treeFromList (
    +          map treeFromList
    +             [ map treeFromList ["YO","DAWG"]
    +             , map treeFromList ["I","HEARD"]
    +             , map treeFromList ["I","HEARD"]
    +             , map treeFromList ["YOU","LIKE","TREES"] ]))
    +

    and gives:

    +
    Binary tree of Binary trees of Char binary trees:
    +< < < 'Y'
    +: : : `--'O'
    +: : `--< 'D'
    +: :    : |--'A'
    +: :    : `--'W'
    +: :    :    `--'G'
    +: |--< < 'I'
    +: |  : `--< 'H'
    +: |  :    : |--'E'
    +: |  :    : |  `--'A'
    +: |  :    : |     `--'D'
    +: |  :    : `--'R'
    +: `--< < 'Y'
    +:    : : `--'O'
    +:    : :    `--'U'
    +:    : `--< 'L'
    +:    :    : `--'I'
    +:    :    :    |--'E'
    +:    :    :    `--'K'
    +:    :    `--< 'T'
    +:    :       : `--'R'
    +:    :       :    |--'E'
    +:    :       :    `--'S'
    +

    Notice how duplicate trees aren’t inserted; there is only one tree corresponding to "I","HEARD". We have this for (almost) free, because we have declared Tree to be an instance of Eq.

    +

    See how awesome this structure is. We can make trees containing not only integers, strings and chars, but also other trees. And we can even make a tree containing a tree of trees!

    +

    02_Hard_Part/31_Trees.lhs

    +
    +

    02_Hard_Part/40_Infinites_Structures.lhs

    +

    +Infinite Structures +

    + +

    Escher

    +

    It is often stated that Haskell is lazy.

    +

    In fact, if you are a bit pedantic, you should state that Haskell is non-strict. Laziness is just a common implementation for non-strict languages.

    +

    Then what does not-strict means? From the Haskell wiki:

    +
    +

    Reduction (the mathematical term for evaluation) proceeds from the outside in.

    +

    so if you have (a+(b*c)) then you first reduce + first, then you reduce the inner (b*c)

    +
    +

    For example in Haskell you can do:

    +
    +
    -- numbers = [1,2,..]
    +numbers :: [Integer]
    +numbers = 0:map (1+) numbers
    +
    +take' n [] = []
    +take' 0 l = []
    +take' n (x:xs) = x:take' (n-1) xs
    +
    +main = print $ take' 10 numbers
    +
    +

    And it stops.

    +

    How?

    +

    Instead of trying to evaluate numbers entirely, it evaluates elements only when needed.

    +

    Also, note in Haskell there is a notation for infinite lists

    +
    [1..]   ⇔ [1,2,3,4...]
    +[1,3..] ⇔ [1,3,5,7,9,11...]
    +

    And most functions will work with them. Also, there is a built-in function take which is equivalent to our take'.

    +

    02_Hard_Part/40_Infinites_Structures.lhs

    +
    +

    02_Hard_Part/41_Infinites_Structures.lhs

    +
    + +

    This code is mostly the same as the previous one.

    +
    +
    import Debug.Trace (trace)
    +import Data.List
    +data BinTree a = Empty 
    +                 | Node a (BinTree a) (BinTree a) 
    +                  deriving (Eq,Ord)
    +
    +
    +
    -- declare BinTree a to be an instance of Show
    +instance (Show a) => Show (BinTree a) where
    +  -- will start by a '<' before the root
    +  -- and put a : a begining of line
    +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
    +    where
    +    treeshow pref Empty = ""
    +    treeshow pref (Node x Empty Empty) = 
    +                  (pshow pref x)
    +
    +    treeshow pref (Node x left Empty) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " left)
    +
    +    treeshow pref (Node x Empty right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    treeshow pref (Node x left right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "|--" "|  " left) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- show a tree using some prefixes to make it nice
    +    showSon pref before next t = 
    +                  pref ++ before ++ treeshow (pref ++ next) t
    +
    +    -- pshow replace "\n" by "\n"++pref
    +    pshow pref x = replace '\n' ("\n"++pref) (" " ++ show x)
    +
    +    -- replace on char by another string
    +    replace c new string =
    +      concatMap (change c new) string
    +      where
    +          change c new x 
    +              | x == c = new
    +              | otherwise = x:[] -- "x"
    +
    +
    + +

    Suppose we don’t mind having an ordered binary tree. Here is an infinite binary tree:

    +
    +
    nullTree = Node 0 nullTree nullTree
    +
    +

    A complete binary tree where each node is equal to 0. Now I will prove you can manipulate this object using the following function:

    +
    +
    -- take all element of a BinTree 
    +-- up to some depth
    +treeTakeDepth _ Empty = Empty
    +treeTakeDepth 0 _     = Empty
    +treeTakeDepth n (Node x left right) = let
    +          nl = treeTakeDepth (n-1) left
    +          nr = treeTakeDepth (n-1) right
    +          in
    +              Node x nl nr
    +
    +

    See what occurs for this program:

    +
    main = print $ treeTakeDepth 4 nullTree
    +

    This code compiles, runs and stops giving the following result:

    +
    <  0
    +: |-- 0
    +: |  |-- 0
    +: |  |  |-- 0
    +: |  |  `-- 0
    +: |  `-- 0
    +: |     |-- 0
    +: |     `-- 0
    +: `-- 0
    +:    |-- 0
    +:    |  |-- 0
    +:    |  `-- 0
    +:    `-- 0
    +:       |-- 0
    +:       `-- 0
    +

    Just to heat up your neurones a bit more, let’s make a slightly more interesting tree:

    +
    +
    iTree = Node 0 (dec iTree) (inc iTree)
    +        where
    +           dec (Node x l r) = Node (x-1) (dec l) (dec r) 
    +           inc (Node x l r) = Node (x+1) (inc l) (inc r) 
    +
    +

    Another way to create this tree is to use a higher order function. This function should be similar to map, but should work on BinTree instead of list. Here is such a function:

    +
    +
    -- apply a function to each node of Tree
    +treeMap :: (a -> b) -> BinTree a -> BinTree b
    +treeMap f Empty = Empty
    +treeMap f (Node x left right) = Node (f x) 
    +                                     (treeMap f left) 
    +                                     (treeMap f right)
    +
    +

    Hint: I won’t talk more about this here. If you are interested by the generalization of map to other data structures, search for functor and fmap.

    +

    Our definition is now:

    +
    +
    infTreeTwo :: BinTree Int
    +infTreeTwo = Node 0 (treeMap (\x -> x-1) infTreeTwo) 
    +                    (treeMap (\x -> x+1) infTreeTwo) 
    +
    +

    Look at the result for

    +
    main = print $ treeTakeDepth 4 infTreeTwo
    +
    <  0
    +: |-- -1
    +: |  |-- -2
    +: |  |  |-- -3
    +: |  |  `-- -1
    +: |  `-- 0
    +: |     |-- -1
    +: |     `-- 1
    +: `-- 1
    +:    |-- 0
    +:    |  |-- -1
    +:    |  `-- 1
    +:    `-- 2
    +:       |-- 1
    +:       `-- 3
    +
    + +
    +
    main = do
    +  print $ treeTakeDepth 4 nullTree
    +  print $ treeTakeDepth 4 infTreeTwo
    +
    +
    + +

    02_Hard_Part/41_Infinites_Structures.lhs

    +

    +Hell Difficulty Part +

    + +

    Congratulations for getting so far! Now, some of the really hardcore stuff can start.

    +

    If you are like me, you should get the functional style. You should also understand a bit more the advantages of laziness by default. But you also don’t really understand where to start in order to make a real program. And in particular:

    +
      +
    • How do you deal with effects?
    • +
    • Why is there a strange imperative-like notation for dealing with IO?
    • +
    +

    Be prepared, the answers might be complex. But they all be very rewarding.

    +
    +

    03_Hell/01_IO/01_progressive_io_example.lhs

    +

    +Deal With IO +

    + +

    Magritte, Carte blanche

    +
    +

    tl;dr:

    +

    A typical function doing IO looks a lot like an imperative program:

    +
    f :: IO a
    +f = do
    +  x <- action1
    +  action2 x
    +  y <- action3
    +  action4 x y
    +
      +
    • To set a value to an object we use <- .
    • +
    • The type of each line is IO *; in this example:
    • +
    • action1 :: IO b
    • +
    • action2 x :: IO ()
    • +
    • action3 :: IO c
    • +
    • action4 x y :: IO a
    • +
    • x :: b, y :: c
    • +
    • Few objects have the type IO a, this should help you choose. In particular you cannot use pure functions directly here. To use pure functions you could do action2 (purefunction x) for example.
    • +
    +
    +

    In this section, I will explain how to use IO, not how it works. You’ll see how Haskell separates the pure from the impure parts of the program.

    +

    Don’t stop because you’re trying to understand the details of the syntax. Answers will come in the next section.

    +

    What to achieve?

    +
    +

    Ask a user to enter a list of numbers. Print the sum of the numbers

    +
    +
    +
    toList :: String -> [Integer]
    +toList input = read ("[" ++ input ++ "]")
    +
    +main = do
    +  putStrLn "Enter a list of numbers (separated by comma):"
    +  input <- getLine
    +  print $ sum (toList input)
    +
    +

    It should be straightforward to understand the behavior of this program. Let’s analyze the types in more detail.

    +
    putStrLn :: String -> IO ()
    +getLine  :: IO String
    +print    :: Show a => a -> IO ()
    +

    Or more interestingly, we note that each expression in the do block has a type of IO a.

    +
    +main = do
    +  putStrLn "Enter ... " :: IO ()
    +  getLine               :: IO String
    +  print Something       :: IO ()
    +
    + +

    We should also pay attention to the effect of the <- symbol.

    +
    do
    + x <- something
    +

    If something :: IO a then x :: a.

    +

    Another important note about using IO. All lines in a do block must be of one of the two forms:

    +
    action1             :: IO a
    +                    -- in this case, generally a = ()
    +

    or

    +
    value <- action2    -- where
    +                    -- bar z t :: IO b
    +                    -- value   :: b
    +

    These two kinds of line will correspond to two different ways of sequencing actions. The meaning of this sentence should be clearer by the end of the next section.

    +

    03_Hell/01_IO/01_progressive_io_example.lhs

    +
    +

    03_Hell/01_IO/02_progressive_io_example.lhs

    +

    Now let’s see how this program behaves. For example, what occur if the user enter something strange? Let’s try:

    +
        % runghc 02_progressive_io_example.lhs
    +    Enter a list of numbers (separated by comma):
    +    foo
    +    Prelude.read: no parse
    +

    Argh! An evil error message and a crash! The first evolution will be to answer with a more friendly message.

    +

    In order to do this, we must detect that something went wrong. Here is one way to do this. Use the type Maybe. It is a very common type in Haskell.

    +
    +
    import Data.Maybe
    +
    +

    What is this thing? Maybe is a type which takes one parameter. Its definition is:

    +
    data Maybe a = Nothing | Just a
    +

    This is a nice way to tell there was an error while trying to create/compute a value. The maybeRead function is a great example of this. This is a function similar to the function read5, but if something goes wrong the returned value is Nothing. If the value is right, it returns Just <the value>. Don’t try to understand too much of this function. I use a lower level function than read; reads.

    +
    +
    maybeRead :: Read a => String -> Maybe a
    +maybeRead s = case reads s of
    +                  [(x,"")]    -> Just x
    +                  _           -> Nothing
    +
    +

    Now to be a bit more readable, we define a function which goes like this: If the string has the wrong format, it will return Nothing. Otherwise, for example for “1,2,3”, it will return Just [1,2,3].

    +
    +
    getListFromString :: String -> Maybe [Integer]
    +getListFromString str = maybeRead $ "[" ++ str ++ "]"
    +
    +

    We simply have to test the value in our main function.

    +
    +
    main :: IO ()
    +main = do
    +  putStrLn "Enter a list of numbers (separated by comma):"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> print (sum l)
    +          Nothing -> error "Bad format. Good Bye."
    +
    +

    In case of error, we display a nice error message.

    +

    Note that the type of each expression in the main’s do block remains of the form IO a. The only strange construction is error. I’ll say error msg will simply take the needed type (here IO ()).

    +

    One very important thing to note is the type of all the functions defined so far. There is only one function which contains IO in its type: main. This means main is impure. But main uses getListFromString which is pure. It is then clear just by looking at declared types which functions are pure and which are impure.

    +

    Why does purity matter? I certainly forget many advantages, but the three main reasons are:

    +
      +
    • It is far easier to think about pure code than impure one.
    • +
    • Purity protects you from all the hard to reproduce bugs due to side effects.
    • +
    • You can evaluate pure functions in any order or in parallel without risk.
    • +
    +

    This is why you should generally put as most code as possible inside pure functions.

    +

    03_Hell/01_IO/02_progressive_io_example.lhs

    +
    +

    03_Hell/01_IO/03_progressive_io_example.lhs

    +

    Our next evolution will be to prompt the user again and again until she enters a valid answer.

    +

    We keep the first part:

    +
    +
    import Data.Maybe
    +
    +maybeRead :: Read a => String -> Maybe a
    +maybeRead s = case reads s of
    +                  [(x,"")]    -> Just x
    +                  _           -> Nothing
    +getListFromString :: String -> Maybe [Integer]
    +getListFromString str = maybeRead $ "[" ++ str ++ "]"
    +
    +

    Now, we create a function which will ask the user for an list of integers until the input is right.

    +
    +
    askUser :: IO [Integer]
    +askUser = do
    +  putStrLn "Enter a list of numbers (separated by comma):"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> return l
    +          Nothing -> askUser
    +
    +

    This function is of type IO [Integer]. Such a type means that we retrieved a value of type [Integer] through some IO actions. Some people might explain while waving their hands:

    +
    +

    «This is an [Integer] inside an IO»

    +
    +

    If you want to understand the details behind all of this, you’ll have to read the next section. But sincerely, if you just want to use IO. Just practice a little and remember to think about the type.

    +

    Finally our main function is quite simpler:

    +
    +
    main :: IO ()
    +main = do
    +  list <- askUser
    +  print $ sum list
    +
    +

    We have finished with our introduction to IO. This was quite fast. Here are the main things to remember:

    +
      +
    • in the do bloc, each expression must have the type IO a. You are then limited in the number of expressions available. For example, getLine, print, putStrLn, etc…
    • +
    • Try to externalize the pure functions as much as possible.
    • +
    • the IO a type means: an IO action which returns an element of type a. IO represents actions; under the hood, IO a is the type of a function. Read the next section if you are curious.
    • +
    +

    If you practice a bit, you should be able to use IO.

    +
    +

    Exercises:

    +
      +
    • Make a program that sums all of its arguments. Hint: use the function getArgs.
    • +
    +
    +

    03_Hell/01_IO/03_progressive_io_example.lhs

    +

    +IO trick explained +

    + +

    Magritte, ceci n

    +
    +

    Here is a tl;dr: for this section.

    +

    To separate pure and impure parts, main is defined as a function which modifies the state of the world

    +
    main :: World -> World
    +

    A function is guaranteed to have side effects only if it has this type. But look at a typical main function:

    +
    main w0 =
    +    let (v1,w1) = action1 w0 in
    +    let (v2,w2) = action2 v1 w1 in
    +    let (v3,w3) = action3 v2 w2 in
    +    action4 v3 w3
    +

    We have a lot of temporary elements (here w1, w2 and w3) which must be passed on to the next action.

    +

    We create a function bind or (>>=). With bind we don’t need temporary names anymore.

    +
    main =
    +  action1 >>= action2 >>= action3 >>= action4
    +

    Bonus: Haskell has syntactical sugar for us:

    +
    main = do
    +  v1 <- action1
    +  v2 <- action2 v1
    +  v3 <- action3 v2
    +  action4 v3
    +
    +

    Why did we use this strange syntax, and what exactly is this IO type? It looks a bit like magic.

    +

    For now let’s just forget all about the pure parts of our program, and focus on the impure parts:

    +
    askUser :: IO [Integer]
    +askUser = do
    +  putStrLn "Enter a list of numbers (separated by commas):"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> return l
    +          Nothing -> askUser
    +
    +main :: IO ()
    +main = do
    +  list <- askUser
    +  print $ sum list
    +

    First remark; it looks like an imperative structure. Haskell is powerful enough to make impure code look imperative. For example, if you wish you could create a while in Haskell. In fact, for dealing with IO, imperative style is generally more appropriate.

    +

    But you should had noticed the notation is a bit unusual. Here is why, in detail.

    +

    In an impure language, the state of the world can be seen as a huge hidden global variable. This hidden variable is accessible by all functions of your language. For example, you can read and write a file in any function. The fact that a file exists or not can be seen as different states of the world.

    +

    For Haskell this state is not hidden. It is explicitly said main is a function that potentially changes the state of the world. Its type is then something like:

    +
    main :: World -> World
    +

    Not all functions may have access to this variable. Those which have access to this variable are impure. Functions to which the world variable isn’t provided are pure6.

    +

    Haskell considers the state of the world as an input variable to main. But the real type of main is closer to this one7:

    +
    main :: World -> ((),World)
    +

    The () type is the null type. Nothing to see here.

    +

    Now let’s rewrite our main function with this in mind:

    +
    main w0 =
    +    let (list,w1) = askUser w0 in
    +    let (x,w2) = print (sum list,w1) in
    +    x
    +

    First, we note that all functions which have side effects must have the type:

    +
    World -> (a,World)
    +

    Where a is the type of the result. For example, a getChar function should have the type World -> (Char,World).

    +

    Another thing to note is the trick to fix the order of evaluation. In Haskell, in order to evaluate f a b, you have many choices:

    +
      +
    • first eval a then b then f a b
    • +
    • first eval b then a then f a b.
    • +
    • eval a and b in parallel then f a b
    • +
    +

    This is true, because we should work in a pure language.

    +

    Now, if you look at the main function, it is clear you must eval the first line before the second one since, to evaluate the second line you have to get a parameter given by the evaluation of the first line.

    +

    Such trick works nicely. The compiler will at each step provide a pointer to a new real world id. Under the hood, print will evaluate as:

    +
      +
    • print something on the screen
    • +
    • modify the id of the world
    • +
    • evaluate as ((),new world id).
    • +
    +

    Now, if you look at the style of the main function, it is clearly awkward. Let’s try to do the same to the askUser function:

    +
    askUser :: World -> ([Integer],World)
    +

    Before:

    +
    askUser :: IO [Integer]
    +askUser = do
    +  putStrLn "Enter a list of numbers:"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> return l
    +          Nothing -> askUser
    +

    After:

    +
    askUser w0 =
    +    let (_,w1)     = putStrLn "Enter a list of numbers:" in
    +    let (input,w2) = getLine w1 in
    +    let (l,w3)     = case getListFromString input of
    +                      Just l   -> (l,w2)
    +                      Nothing  -> askUser w2
    +    in
    +        (l,w3)
    +

    This is similar, but awkward. Look at all these temporary w? names.

    +

    The lesson, is, naive IO implementation in Pure functional languages is awkward!

    +

    Fortunately, there is a better way to handle this problem. We see a pattern. Each line is of the form:

    +
    let (y,w') = action x w in
    +

    Even if for some line the first x argument isn’t needed. The output type is a couple, (answer, newWorldValue). Each function f must have a type similar to:

    +
    f :: World -> (a,World)
    +

    Not only this, but we can also note that we always follow the same usage pattern:

    +
    let (y,w1) = action1 w0 in
    +let (z,w2) = action2 w1 in
    +let (t,w3) = action3 w2 in
    +...
    +

    Each action can take from 0 to n parameters. And in particular, each action can take a parameter from the result of a line above.

    +

    For example, we could also have:

    +
    let (_,w1) = action1 x w0   in
    +let (z,w2) = action2 w1     in
    +let (_,w3) = action3 x z w2 in
    +...
    +

    And of course actionN w :: (World) -> (a,World).

    +
    +

    IMPORTANT, there are only two important patterns to consider:

    +
    let (x,w1) = action1 w0 in
    +let (y,w2) = action2 x w1 in
    +

    and

    +
    let (_,w1) = action1 w0 in
    +let (y,w2) = action2 w1 in
    +
    +

    Jocker pencil trick

    +

    Now, we will do a magic trick. We will make the temporary world symbol “disappear”. We will bind the two lines. Let’s define the bind function. Its type is quite intimidating at first:

    +
    bind :: (World -> (a,World))
    +        -> (a -> (World -> (b,World)))
    +        -> (World -> (b,World))
    +

    But remember that (World -> (a,World)) is the type for an IO action. Now let’s rename it for clarity:

    +
    type IO a = World -> (a, World)
    +

    Some example of functions:

    +
    getLine :: IO String
    +print :: Show a => a -> IO ()
    +

    getLine is an IO action which takes a world as parameter and returns a couple (String,World). Which can be summarized as: getLine is of type IO String. Which we also see as, an IO action which will return a String “embeded inside an IO”.

    +

    The function print is also interesting. It takes one argument which can be shown. In fact it takes two arguments. The first is the value to print and the other is the state of world. It then returns a couple of type ((),World). This means it changes the state of the world, but doesn’t yield anymore data.

    +

    This type helps us simplify the type of bind:

    +
    bind :: IO a
    +        -> (a -> IO b)
    +        -> IO b
    +

    It says that bind takes two IO actions as parameter and return another IO action.

    +

    Now, remember the important patterns. The first was:

    +
    let (x,w1) = action1 w0 in
    +let (y,w2) = action2 x w1 in
    +(y,w2)
    +

    Look at the types:

    +
    action1  :: IO a
    +action2  :: a -> IO b
    +(y,w2)   :: IO b
    +

    Doesn’t it seem familiar?

    +
    (bind action1 action2) w0 =
    +    let (x, w1) = action1 w0
    +        (y, w2) = action2 x w1
    +    in  (y, w2)
    +

    The idea is to hide the World argument with this function. Let’s go: As an example imagine if we wanted to simulate:

    +
    let (line1,w1) = getLine w0 in
    +let ((),w2) = print line1 in
    +((),w2)
    +

    Now, using the bind function:

    +
    (res,w2) = (bind getLine (\l -> print l)) w0
    +

    As print is of type (World -> ((),World)), we know res = () (null type). If you didn’t see what was magic here, let’s try with three lines this time.

    +
    let (line1,w1) = getLine w0 in
    +let (line2,w2) = getLine w1 in
    +let ((),w3) = print (line1 ++ line2) in
    +((),w3)
    +

    Which is equivalent to:

    +
    (res,w3) = bind getLine (\line1 ->
    +             bind getLine (\line2 ->
    +               print (line1 ++ line2)))
    +

    Didn’t you notice something? Yes, no temporary World variables are used anywhere! This is MA. GIC.

    +

    We can use a better notation. Let’s use (>>=) instead of bind. (>>=) is an infix function like (+); reminder 3 + 4 ⇔ (+) 3 4

    +
    (res,w3) = getLine >>=
    +           \line1 -> getLine >>=
    +           \line2 -> print (line1 ++ line2)
    +

    Ho Ho Ho! Happy Christmas Everyone! Haskell has made syntactical sugar for us:

    +
    do
    +  x <- action1
    +  y <- action2
    +  z <- action3
    +  ...
    +

    Is replaced by:

    +
    action1 >>= \x ->
    +action2 >>= \y ->
    +action3 >>= \z ->
    +...
    +

    Note you can use x in action2 and x and y in action3.

    +

    But what about the lines not using the <-? Easy, another function blindBind:

    +
    blindBind :: IO a -> IO b -> IO b
    +blindBind action1 action2 w0 =
    +    bind action (\_ -> action2) w0
    +

    I didn’t simplify this definition for clarity purpose. Of course we can use a better notation, we’ll use the (>>) operator.

    +

    And

    +
    do
    +    action1
    +    action2
    +    action3
    +

    Is transformed into

    +
    action1 >>
    +action2 >>
    +action3
    +

    Also, another function is quite useful.

    +
    putInIO :: a -> IO a
    +putInIO x = IO (\w -> (x,w))
    +

    This is the general way to put pure values inside the “IO context”. The general name for putInIO is return. This is quite a bad name when you learn Haskell. return is very different from what you might be used to.

    +
    +

    03_Hell/01_IO/21_Detailled_IO.lhs

    +

    To finish, let’s translate our example:

    +
    
    +askUser :: IO [Integer]
    +askUser = do
    +  putStrLn "Enter a list of numbers (separated by commas):"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> return l
    +          Nothing -> askUser
    +
    +main :: IO ()
    +main = do
    +  list <- askUser
    +  print $ sum list
    +

    Is translated into:

    +
    +
    import Data.Maybe
    +
    +maybeRead :: Read a => String -> Maybe a
    +maybeRead s = case reads s of
    +                  [(x,"")]    -> Just x
    +                  _           -> Nothing
    +getListFromString :: String -> Maybe [Integer]
    +getListFromString str = maybeRead $ "[" ++ str ++ "]"
    +askUser :: IO [Integer]
    +askUser = 
    +    putStrLn "Enter a list of numbers (sep. by commas):" >>
    +    getLine >>= \input ->
    +    let maybeList = getListFromString input in
    +      case maybeList of
    +        Just l -> return l
    +        Nothing -> askUser
    +
    +main :: IO ()
    +main = askUser >>=
    +  \list -> print $ sum list
    +
    +

    You can compile this code to verify it keeps working.

    +

    Imagine what it would look like without the (>>) and (>>=).

    +

    03_Hell/01_IO/21_Detailled_IO.lhs

    +
    +

    03_Hell/02_Monads/10_Monads.lhs

    +

    +Monads +

    + +

    Dali, reve. It represents a weapon out of the mouth of a tiger, itself out of the mouth of another tiger, itself out of the mouth of a fish itself out of a grenade. I could have choosen a picture of the Human centipede as it is a very good representation of what a monad really is. But just to thing about it, I find this disgusting and that wasn

    +

    Now the secret can be revealed: IO is a monad. Being a monad means you have access to some syntactical sugar with the do notation. But mainly, you have access to a coding pattern which will ease the flow of your code.

    +
    +

    Important remarks:

    +
      +
    • Monad are not necessarily about effects! There are a lot of pure monads.
    • +
    • Monad are more about sequencing
    • +
    +
    +

    For the Haskell language Monad is a type class. To be an instance of this type class, you must provide the functions (>>=) and return. The function (>>) will be derived from (>>=). Here is how the type class Monad is declared (mostly):

    +
    class Monad m  where
    +  (>>=) :: m a -> (a -> m b) -> m b
    +  return :: a -> m a
    +
    +  (>>) :: m a -> m b -> m b
    +  f >> g = f >>= \_ -> g
    +
    +  -- You should generally safely ignore this function
    +  -- which I believe exists for historical reason
    +  fail :: String -> m a
    +  fail = error
    +
    +

    Remarks:

    +
      +
    • the keyword class is not your friend. A Haskell class is not a class like in object model. A Haskell class has a lot of similarities with Java interfaces. A better word should have been typeclass. That means a set of types. For a type to belong to a class, all functions of the class must be provided for this type.
    • +
    • In this particular example of type class, the type m must be a type that takes an argument. for example IO a, but also Maybe a, [a], etc…
    • +
    • To be a useful monad, your function must obey some rules. If your construction does not obey these rules strange things might happens:
    • +
    +

    ~ return a >>= k == k a m >>= return == m m >>= (-> k x >>= h) == (m >>= k) >>= h ~

    +
    +

    +Maybe is a monad +

    + +

    There are a lot of different types that are instance of Monad. One of the easiest to describe is Maybe. If you have a sequence of Maybe values, you can use monads to manipulate them. It is particularly useful to remove very deep if..then..else.. constructions.

    +

    Imagine a complex bank operation. You are eligible to gain about 700€ only if you can afford to follow a list of operations without being negative.

    +
    +
    deposit  value account = account + value
    +withdraw value account = account - value
    +
    +eligible :: (Num a,Ord a) => a -> Bool
    +eligible account =
    +  let account1 = deposit 100 account in
    +    if (account1 < 0)
    +    then False
    +    else
    +      let account2 = withdraw 200 account1 in
    +      if (account2 < 0)
    +      then False
    +      else
    +        let account3 = deposit 100 account2 in
    +        if (account3 < 0)
    +        then False
    +        else
    +          let account4 = withdraw 300 account3 in
    +          if (account4 < 0)
    +          then False
    +          else
    +            let account5 = deposit 1000 account4 in
    +            if (account5 < 0)
    +            then False
    +            else
    +              True
    +
    +main = do
    +  print $ eligible 300 -- True
    +  print $ eligible 299 -- False
    +
    +

    03_Hell/02_Monads/10_Monads.lhs

    +
    +

    03_Hell/02_Monads/11_Monads.lhs

    +

    Now, let’s make it better using Maybe and the fact that it is a Monad

    +
    +
    deposit :: (Num a) => a -> a -> Maybe a
    +deposit value account = Just (account + value)
    +
    +withdraw :: (Num a,Ord a) => a -> a -> Maybe a
    +withdraw value account = if (account < value) 
    +                         then Nothing 
    +                         else Just (account - value)
    +
    +eligible :: (Num a, Ord a) => a -> Maybe Bool
    +eligible account = do
    +  account1 <- deposit 100 account 
    +  account2 <- withdraw 200 account1 
    +  account3 <- deposit 100 account2 
    +  account4 <- withdraw 300 account3 
    +  account5 <- deposit 1000 account4
    +  Just True
    +
    +main = do
    +  print $ eligible 300 -- Just True
    +  print $ eligible 299 -- Nothing
    +
    +

    03_Hell/02_Monads/11_Monads.lhs

    +
    +

    03_Hell/02_Monads/12_Monads.lhs

    +

    Not bad, but we can make it even better:

    +
    +
    deposit :: (Num a) => a -> a -> Maybe a
    +deposit value account = Just (account + value)
    +
    +withdraw :: (Num a,Ord a) => a -> a -> Maybe a
    +withdraw value account = if (account < value) 
    +                         then Nothing 
    +                         else Just (account - value)
    +
    +eligible :: (Num a, Ord a) => a -> Maybe Bool
    +eligible account =
    +  deposit 100 account >>=
    +  withdraw 200 >>=
    +  deposit 100  >>=
    +  withdraw 300 >>=
    +  deposit 1000 >>
    +  return True
    +
    +main = do
    +  print $ eligible 300 -- Just True
    +  print $ eligible 299 -- Nothing
    +
    +

    We have proven that Monads are a good way to make our code more elegant. Note this idea of code organization, in particular for Maybe can be used in most imperative language. In fact, this is the kind of construction we make naturally.

    +
    +

    An important remark:

    +

    The first element in the sequence being evaluated to Nothing will stop the complete evaluation. This means you don’t execute all lines. You have this for free, thanks to laziness.

    +
    +

    You could also replay these example with the definition of (>>=) for Maybe in mind:

    +
    instance Monad Maybe where
    +    (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
    +    Nothing  >>= _  = Nothing
    +    (Just x) >>= f  = f x
    +
    +    return x = Just x
    +

    The Maybe monad proved to be useful while being a very simple example. We saw the utility of the IO monad. But now a cooler example, lists.

    +

    03_Hell/02_Monads/12_Monads.lhs

    +
    +

    03_Hell/02_Monads/13_Monads.lhs

    +

    +The list monad +

    + +

    Golconde de Magritte

    +

    The list monad helps us to simulate non deterministic computations. Here we go:

    +
    +
    import Control.Monad (guard)
    +
    +allCases = [1..10]
    +
    +resolve :: [(Int,Int,Int)]
    +resolve = do
    +              x <- allCases
    +              y <- allCases
    +              z <- allCases
    +              guard $ 4*x + 2*y < z
    +              return (x,y,z)
    +
    +main = do
    +  print resolve
    +
    +

    MA. GIC. :

    +
    [(1,1,7),(1,1,8),(1,1,9),(1,1,10),(1,2,9),(1,2,10)]
    +

    For the list monad, there is also a syntactical sugar:

    +
    +
      print $ [ (x,y,z) | x <- allCases,
    +                      y <- allCases,
    +                      z <- allCases,
    +                      4*x + 2*y < z ]
    +
    +

    I won’t list all the monads, but there are many monads. Using monads simplifies the manipulation of several notions in pure languages. In particular, monad are very useful for:

    +
      +
    • IO,
    • +
    • non deterministic computation,
    • +
    • generating pseudo random numbers,
    • +
    • keeping configuration state,
    • +
    • writing state,
    • +
    • +
    +

    If you have followed me until here, then you’ve done it! You know monads8!

    +

    03_Hell/02_Monads/13_Monads.lhs

    +

    +Appendix +

    + +

    This section is not so much about learning Haskell. It is just here to discuss some details further.

    +
    +

    04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs

    +

    +More on Infinite Tree +

    + +

    In the section Infinite Structures we saw some simple constructions. Unfortunately we removed two properties from our tree:

    +
      +
    1. no duplicate node value
    2. +
    3. well ordered tree
    4. +
    +

    In this section we will try to keep the first property. Concerning the second one, we must relax it but we’ll discuss how to keep it as much as possible.

    +
    + +

    This code is mostly the same as the one in the tree section.

    +
    +
    import Data.List
    +data BinTree a = Empty 
    +                 | Node a (BinTree a) (BinTree a) 
    +                  deriving (Eq,Ord)
    +
    +-- declare BinTree a to be an instance of Show
    +instance (Show a) => Show (BinTree a) where
    +  -- will start by a '<' before the root
    +  -- and put a : a begining of line
    +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
    +    where
    +    treeshow pref Empty = ""
    +    treeshow pref (Node x Empty Empty) = 
    +                  (pshow pref x)
    +
    +    treeshow pref (Node x left Empty) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " left)
    +
    +    treeshow pref (Node x Empty right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    treeshow pref (Node x left right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "|--" "|  " left) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- show a tree using some prefixes to make it nice
    +    showSon pref before next t = 
    +                  pref ++ before ++ treeshow (pref ++ next) t
    +
    +    -- pshow replace "\n" by "\n"++pref
    +    pshow pref x = replace '\n' ("\n"++pref) (show x)
    +
    +    -- replace on char by another string
    +    replace c new string =
    +      concatMap (change c new) string
    +      where
    +          change c new x 
    +              | x == c = new
    +              | otherwise = x:[] -- "x"
    +
    +
    + +

    Our first step is to create some pseudo-random number list:

    +
    +
    shuffle = map (\x -> (x*3123) `mod` 4331) [1..]
    +
    +

    Just as a reminder, here is the definition of treeFromList

    +
    +
    treeFromList :: (Ord a) => [a] -> BinTree a
    +treeFromList []    = Empty
    +treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
    +                             (treeFromList (filter (>x) xs))
    +
    +

    and treeTakeDepth:

    +
    +
    treeTakeDepth _ Empty = Empty
    +treeTakeDepth 0 _     = Empty
    +treeTakeDepth n (Node x left right) = let
    +          nl = treeTakeDepth (n-1) left
    +          nr = treeTakeDepth (n-1) right
    +          in
    +              Node x nl nr
    +
    +

    See the result of:

    +
    +
    main = do
    +      putStrLn "take 10 shuffle"
    +      print $ take 10 shuffle
    +      putStrLn "\ntreeTakeDepth 4 (treeFromList shuffle)"
    +      print $ treeTakeDepth 4 (treeFromList shuffle)
    +
    +
    % runghc 02_Hard_Part/41_Infinites_Structures.lhs
    +take 10 shuffle
    +[3123,1915,707,3830,2622,1414,206,3329,2121,913]
    +treeTakeDepth 4 (treeFromList shuffle)
    +
    +< 3123
    +: |--1915
    +: |  |--707
    +: |  |  |--206
    +: |  |  `--1414
    +: |  `--2622
    +: |     |--2121
    +: |     `--2828
    +: `--3830
    +:    |--3329
    +:    |  |--3240
    +:    |  `--3535
    +:    `--4036
    +:       |--3947
    +:       `--4242
    +

    Yay! It ends! Beware though, it will only work if you always have something to put into a branch.

    +

    For example

    +
    treeTakeDepth 4 (treeFromList [1..]) 
    +

    will loop forever. Simply because it will try to access the head of filter (<1) [2..]. But filter is not smart enought to understand that the result is the empty list.

    +

    Nonetheless, it is still a very cool example of what non strict programs have to offer.

    +

    Left as an exercise to the reader:

    +
      +
    • Prove the existence of a number n so that treeTakeDepth n (treeFromList shuffle) will enter an infinite loop.
    • +
    • Find an upper bound for n.
    • +
    • Prove there is no shuffle list so that, for any depth, the program ends.
    • +
    +

    04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs

    +
    +

    04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs

    +
    + +

    This code is mostly the same as the preceding one.

    +
    +
    import Debug.Trace (trace)
    +import Data.List
    +data BinTree a = Empty 
    +                 | Node a (BinTree a) (BinTree a) 
    +                  deriving (Eq,Ord)
    +
    +
    +
    -- declare BinTree a to be an instance of Show
    +instance (Show a) => Show (BinTree a) where
    +  -- will start by a '<' before the root
    +  -- and put a : a begining of line
    +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
    +    where
    +    treeshow pref Empty = ""
    +    treeshow pref (Node x Empty Empty) = 
    +                  (pshow pref x)
    +
    +    treeshow pref (Node x left Empty) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " left)
    +
    +    treeshow pref (Node x Empty right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    treeshow pref (Node x left right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "|--" "|  " left) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- show a tree using some prefixes to make it nice
    +    showSon pref before next t = 
    +                  pref ++ before ++ treeshow (pref ++ next) t
    +
    +    -- pshow replace "\n" by "\n"++pref
    +    pshow pref x = replace '\n' ("\n"++pref) (" " ++ show x)
    +
    +    -- replace on char by another string
    +    replace c new string =
    +      concatMap (change c new) string
    +      where
    +          change c new x 
    +              | x == c = new
    +              | otherwise = x:[] -- "x"
    +
    +treeTakeDepth _ Empty = Empty
    +treeTakeDepth 0 _     = Empty
    +treeTakeDepth n (Node x left right) = let
    +          nl = treeTakeDepth (n-1) left
    +          nr = treeTakeDepth (n-1) right
    +          in
    +              Node x nl nr
    +
    +
    + +

    In order to resolve these problem we will modify slightly our treeFromList and shuffle function.

    +

    A first problem, is the lack of infinite different number in our implementation of shuffle. We generated only 4331 different numbers. To resolve this we make a slightly better shuffle function.

    +
    +
    shuffle = map rand [1..]
    +          where 
    +              rand x = ((p x) `mod` (x+c)) - ((x+c) `div` 2)
    +              p x = m*x^2 + n*x + o -- some polynome
    +              m = 3123    
    +              n = 31
    +              o = 7641
    +              c = 1237
    +
    +

    This shuffle function has the property (hopefully) not to have an upper nor lower bound. But having a better shuffle list isn’t enough not to enter an infinite loop.

    +

    Generally, we cannot decide whether filter (<x) xs is empty. Then to resolve this problem, I’ll authorize some error in the creation of our binary tree. This new version of code can create binary tree which don’t have the following property for some of its nodes:

    +
    +

    Any element of the left (resp. right) branch must all be strictly inferior (resp. superior) to the label of the root.

    +
    +

    Remark it will remains mostly an ordered binary tree. Furthermore, by construction, each node value is unique in the tree.

    +

    Here is our new version of treeFromList. We simply have replaced filter by safefilter.

    +
    +
    treeFromList :: (Ord a, Show a) => [a] -> BinTree a
    +treeFromList []    = Empty
    +treeFromList (x:xs) = Node x left right
    +          where 
    +              left = treeFromList $ safefilter (<x) xs
    +              right = treeFromList $ safefilter (>x) xs
    +
    +

    This new function safefilter is almost equivalent to filter but don’t enter infinite loop if the result is a finite list. If it cannot find an element for which the test is true after 10000 consecutive steps, then it considers to be the end of the search.

    +
    +
    safefilter :: (a -> Bool) -> [a] -> [a]
    +safefilter f l = safefilter' f l nbTry
    +  where
    +      nbTry = 10000
    +      safefilter' _ _ 0 = []
    +      safefilter' _ [] _ = []
    +      safefilter' f (x:xs) n = 
    +                  if f x 
    +                     then x : safefilter' f xs nbTry 
    +                     else safefilter' f xs (n-1) 
    +
    +

    Now run the program and be happy:

    +
    +
    main = do
    +      putStrLn "take 10 shuffle"
    +      print $ take 10 shuffle
    +      putStrLn "\ntreeTakeDepth 8 (treeFromList shuffle)"
    +      print $ treeTakeDepth 8 (treeFromList $ shuffle)
    +
    +

    You should realize the time to print each value is different. This is because Haskell compute each value when it needs it. And in this case, this is when asked to print it on the screen.

    +

    Impressively enough, try to replace the depth from 8 to 100. It will work without killing your RAM! The flow and the memory management is done naturally by Haskell.

    +

    Left as an exercise to the reader:

    +
      +
    • Even with large constant value for deep and nbTry, it seems to work nicely. But in the worst case, it can be exponential. Create a worst case list to give as parameter to treeFromList.
      hint: think about ([0,-1,-1,....,-1,1,-1,...,-1,1,...]).
    • +
    • I first tried to implement safefilter as follow: +
      +  safefilter' f l = if filter f (take 10000 l) == []
      +                    then []
      +                    else filter f l
      +  
      + +Explain why it doesn’t work and can enter into an infinite loop.
    • +
    • Suppose that shuffle is real random list with growing bounds. If you study a bit this structure, you’ll discover that with probability 1, this structure is finite. Using the following code (suppose we could use safefilter' directly as if was not in the where of safefilter) find a definition of f such that with probability 1, treeFromList’ shuffle is infinite. And prove it. Disclaimer, this is only a conjecture.
    • +
    +
    treeFromList' []  n = Empty
    +treeFromList' (x:xs) n = Node x left right
    +    where
    +        left = treeFromList' (safefilter' (<x) xs (f n)
    +        right = treeFromList' (safefilter' (>x) xs (f n)
    +        f = ???
    +

    04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs

    +

    Thanks

    +

    Thanks to /r/haskell and /r/programming. Your comment were most than welcome.

    +

    Particularly, I want to thank Emm a thousand times for the time he spent on correcting my English. Thank you man.

    +
    +
    +
      +
    1. Even if most recent languages try to hide them, they are present.

    2. +
    3. I know I’m cheating. But I will talk about non-strict later.

    4. +
    5. For the brave, a more complete explanation of pattern matching can be found here.

    6. +
    7. You should remark squareEvenSum'' is more efficient that the two other versions. The order of (.) is important.

    8. +
    9. Which itself is very similar to the javascript eval on a string containing JSON).

    10. +
    11. There are some unsafe exceptions to this rule. But you shouldn’t see such use on a real application except maybe for debugging purpose.

    12. +
    13. For the curious the real type is data IO a = IO {unIO :: State# RealWorld -> (# State# RealWorld, a #)}. All the # as to do with optimisation and I swapped the fields in my example. But mostly, the idea is exactly the same.

    14. +
    15. Well, you’ll certainly need to practice a bit to get used to them and to understand when you can use them and create your own. But you already made a big step in this direction.

    16. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2012-02-08 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/Higher-order-function-in-zsh/code/functional.sh b/Scratch/en/blog/Higher-order-function-in-zsh/code/functional.sh new file mode 100644 index 0000000..bf99fa3 --- /dev/null +++ b/Scratch/en/blog/Higher-order-function-in-zsh/code/functional.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env zsh + +# Provide higer-order functions + +# usage: +# +# $ foo(){print "x: $1"} +# $ map foo a b c d +# x: a +# x: b +# x: c +# x: d +function map { + local func_name=$1 + shift + for elem in $@; print -- $(eval $func_name $elem) +} + +# $ bar() { print $(($1 + $2)) } +# $ fold bar 0 1 2 3 4 5 +# 15 +# -- but also +# $ fold bar 0 $( seq 1 100 ) +function fold { + if (($#<2)) { + print -- "ERROR fold use at least 2 arguments" >&2 + return 1 + } + if (($#<3)) { + print -- $2 + return 0 + } else { + local acc + local right + local func_name=$1 + local init_value=$2 + local first_value=$3 + shift 3 + right=$( fold $func_name $init_value $@ ) + acc=$( eval "$func_name $first_value $right" ) + print -- $acc + return 0 + } +} + +# usage: +# +# $ baz() { print $1 | grep baz } +# $ filter baz titi bazaar biz +# bazaar +function filter { + local predicate=$1 + local result + typeset -a result + shift + for elem in $@; do + if eval $predicate $elem >/dev/null; then + result=( $result $elem ) + fi + done + print $result +} diff --git a/Scratch/en/blog/Higher-order-function-in-zsh/index.html b/Scratch/en/blog/Higher-order-function-in-zsh/index.html new file mode 100644 index 0000000..1647ea5 --- /dev/null +++ b/Scratch/en/blog/Higher-order-function-in-zsh/index.html @@ -0,0 +1,273 @@ + + + + + + YBlog - Higher order function in zsh + + + + + + + + + + + + + +
    + + +
    +

    Higher order function in zsh

    +
    +
    +
    +
    +

    Title image

    +
    + +

    UPDATE: Nicholas Sterling had discovered a way to implement anonymous functions Thanks!

    +

    With this last version you should use map if you use external function. mapl to use lambda function. And mapa for arithmetic operations.

    +

    Example:

    +
    $ filterl 'echo $1|grep a >/dev/null' ab cd ef ada
    +ab
    +ada
    +
    +$ folda '$1+$2' {1..5}
    +15
    +
    +$ folda '$1*$2' {1..20}
    +2432902008176640000
    +
    +$ mapl 'echo X $1:t Y' ~/.zsh/functional/src/*
    +X each Y
    +X filter Y
    +X fold Y
    +X map Y
    +
    +$ mapa '$1*2' {1..3}
    +2
    +4
    +6
    +
    +$ mapl 'echo result $1' $(mapa '$1+5' $(mapa '$1*2' {1..3}))
    +result 7
    +result 9
    +result 11
    +
    +

    tl;dr: some simple implementation of higher order function for zsh.

    +
    + +

    Why is it important to have these functions? Simply because, the more I programmed with zsh the more I tended to work using functional programming style.

    +

    The minimal to have better code are the functions map, filter and fold.

    +

    Let’s compare. First a program which convert all gif to png in many different directories of different projects.

    +

    Before ⇒

    +
    # for each directory in projects dir
    +for toProject in /path/to/projects/*(/N); do
    +    # toProject is /path/to/projects/foo
    +    # project become foo (:t for tail)
    +    project=${toProject:t}
    +    for toResource in $toProject/resources/*.gif(.N); do
    +        convert $toResource ${toResource:r}.png && \
    +        \rm -f $toResource
    +    done
    +done
    +
      +
    • The (/N) means to select only directory and not to crash if there isn’t any.
    • +
    • The (.N) means to select only files and not to crash if there isn’t any.
    • +
    • The :t means tail; if toto=/path/to/file.ext then ${toto:t}=file.ext.
    • +
    +

    After ⇒

    +
    gif_to_png() { convert $1 ${1:r}.png && \rm -f $1 }
    +
    +handle_resources() { map gif_to_png $1/resources/*.gif(.N) }
    +
    +map handle_resources /path/to/projects/*(/N)
    +

    No more bloc! It might be a little bit harder to read if you’re not used to functional programming notation. But it is more concise and robusts.

    +

    Another example with some tests.

    +

    Find all files in project not containing an s which their name contains their project name:

    +

    Before ⇒

    +
    for toProject in Projects/*; do
    +    project=$toProject:t
    +    if print -- project | grep -v s >/dev/null
    +    then
    +        print $project
    +        for toResource in $toProject/*(.N); do
    +            if print -- ${toResource:t} | grep $project >/dev/null; then
    +                print -- "X $toResource"
    +            fi
    +        done
    +    fi
    +done
    +

    After ⇒

    +
    contain_no_s() { print $1 | grep -v s }
    +
    +function verify_file_name {                               
    +    local project=$1:t
    +    contains_project_name() { print $1:t | grep $project }
    +    map "print -- X" $(filter contains_project_name $1/*(.N))
    +}
    +
    +map verify_file_name $( filter contain_no_s Projects/* )
    +

    Also, the first verstion is a bit easier to read. But the second one is clearly far superior in architecture. I don’t want to argue why here. Just believe me that the functional programming approach is superior.

    +

    You can find an updated version of the code (thanks to Arash Rouhani). An older version is here thought. Here is the (first version) source code:

    +
    #!/usr/bin/env zsh
    +
    +# Provide higer-order functions 
    +
    +# usage:
    +#
    +# $ foo(){print "x: $1"}
    +# $ map foo a b c d
    +# x: a
    +# x: b
    +# x: c
    +# x: d
    +function map {
    +    local func_name=$1
    +    shift
    +    for elem in $@; print -- $(eval $func_name $elem)
    +}
    +
    +# $ bar() { print $(($1 + $2)) }
    +# $ fold bar 0 1 2 3 4 5
    +# 15
    +# -- but also
    +# $ fold bar 0 $( seq 1 100 )
    +function fold {
    +    if (($#<2)) {
    +        print -- "ERROR fold use at least 2 arguments" >&2
    +        return 1
    +    }
    +    if (($#<3)) {
    +        print -- $2
    +        return 0
    +    } else {
    +        local acc
    +        local right
    +        local func_name=$1
    +        local init_value=$2
    +        local first_value=$3
    +        shift 3
    +        right=$( fold $func_name $init_value $@ )
    +        acc=$( eval "$func_name $first_value $right" )
    +        print -- $acc
    +        return 0
    +    }
    +}
    +
    +# usage:
    +#
    +# $ baz() { print $1 | grep baz }
    +# $ filter baz titi bazaar biz
    +# bazaar
    +function filter {
    +    local predicate=$1
    +    local result
    +    typeset -a result
    +    shift
    +    for elem in $@; do
    +        if eval $predicate $elem >/dev/null; then
    +            result=( $result $elem )
    +        fi
    +    done
    +    print $result
    +}
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-09-28 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/Learn-Vim-Progressively/index.html b/Scratch/en/blog/Learn-Vim-Progressively/index.html new file mode 100644 index 0000000..8d62b2c --- /dev/null +++ b/Scratch/en/blog/Learn-Vim-Progressively/index.html @@ -0,0 +1,379 @@ + + + + + + YBlog - Learn Vim Progressively + + + + + + + + + + + + + +
    + + +
    +

    Learn Vim Progressively

    +
    +
    +
    +
    +

    Über leet use vim!

    +
    + +

    tl;dr: You want to teach yourself vim (the best text editor known to human kind) in the fastest way possible. This is my way of doing it. You start by learning the minimal to survive, then you integrate all the tricks slowly.

    +
    + +

    Vim the Six Billion Dollar editor

    +
    +

    Better, Stronger, Faster.

    +
    +

    Learn vim and it will be your last text editor. There isn’t any better text editor that I know of. It is hard to learn, but incredible to use.

    +

    I suggest you teach yourself Vim in 4 steps:

    +
      +
    1. Survive
    2. +
    3. Feel comfortable
    4. +
    5. Feel Better, Stronger, Faster
    6. +
    7. Use superpowers of vim
    8. +
    +

    By the end of this journey, you’ll become a vim superstar.

    +

    But before we start, just a warning. Learning vim will be painful at first. It will take time. It will be a lot like playing a musical instrument. Don’t expect to be more efficient with vim than with another editor in less than 3 days. In fact it will certainly take 2 weeks instead of 3 days.

    +

    1st Level – Survive

    +
      +
    1. Install vim
    2. +
    3. Launch vim
    4. +
    5. DO NOTHING! Read.
    6. +
    +

    In a standard editor, typing on the keyboard is enough to write something and see it on the screen. Not this time. Vim is in Normal mode. Let’s go to Insert mode. Type the letter i.

    +

    You should feel a bit better. You can type letters like in a standard editor. To get back to Normal mode just press the ESC key.

    +

    You now know how to switch between Insert and Normal mode. And now, here are the commands that you need in order to survive in Normal mode:

    +
    +
      +
    • iInsert mode. Type ESC to return to Normal mode.
    • +
    • x → Delete the char under the cursor
    • +
    • :wq → Save and Quit (:w save, :q quit)
    • +
    • dd → Delete (and copy) the current line
    • +
    • p → Paste
    • +
    +

    Recommended:

    +
      +
    • hjkl (highly recommended but not mandatory) → basic cursor move (←↓↑→). Hint: j looks like a down arrow.
    • +
    • :help <command> → Show help about <command>. You can use :help without a <command> to get general help.
    • +
    +
    +

    Only 5 commands. That is all you need to get started. Once these command start to become natural (maybe after a day or so), you should move on to level 2.

    +

    But first, just a little remark about Normal mode. In standard editors, to copy you have to use the Ctrl key (Ctrl-c generally). In fact, when you press Ctrl, it is as if all of your keys change meaning. Using vim in normal mode is a bit like having the editor automatically press the Ctrl key for you.

    +

    A last word about notations:

    +
      +
    • instead of writing Ctrl-λ, I’ll write <C-λ>.
    • +
    • commands starting with : end with <enter>. For example, when I write :q, I mean :q<enter>.
    • +
    +

    2nd Level – Feel comfortable

    +

    You know the commands required for survival. It’s time to learn a few more commands. These are my suggestions:

    +
      +
    1. Insert mode variations:

      +
      +
        +
      • a → insert after the cursor
      • +
      • o → insert a new line after the current one
      • +
      • O → insert a new line before the current one
      • +
      • cw → replace from the cursor to the end of the word
      • +
      +
    2. +
    3. Basic moves

      +
      +
        +
      • 0 → go to the first column
      • +
      • ^ → go to the first non-blank character of the line
      • +
      • $ → go to the end of line
      • +
      • g_ → go to the last non-blank character of line
      • +
      • /pattern → search for pattern
      • +
      +
    4. +
    5. Copy/Paste

      +
      +
        +
      • P → paste before, remember p is paste after current position.
      • +
      • yy → copy the current line, easier but equivalent to ddP
      • +
      +
    6. +
    7. Undo/Redo

      +
      +
        +
      • u → undo
      • +
      • <C-r> → redo
      • +
      +
    8. +
    9. Load/Save/Quit/Change File (Buffer)

      +
      +
        +
      • :e <path/to/file> → open
      • +
      • :w → save
      • +
      • :saveas <path/to/file> → save to <path/to/file>
      • +
      • :x, ZZ or :wq → save and quit (:x only save if necessary)
      • +
      • :q! → quit without saving, also: :qa! to quit even if there are modified hidden buffers.
      • +
      • :bn (resp. :bp) → show next (resp. previous) file (buffer)
      • +
      +
    10. +
    +

    Take the time to learn all of these command. Once done, you should be able to do every thing you are able to do in other editors. You may still feel a bit awkward. But follow me to the next level and you’ll see why vim is worth the extra work.

    +

    3rd Level – Better. Stronger. Faster.

    +

    Congratulation for reaching this far! Now we can start with the interesting stuff. At level 3, we’ll only talk about commands which are compatible with the old vi editor.

    +

    Better

    +

    Let’s look at how vim could help you to repeat yourself:

    +
      +
    1. . → (dot) will repeat the last command,
    2. +
    3. N<command> → will repeat the command N times.
    4. +
    +

    Some examples, open a file and type:

    +
    +
      +
    • 2dd → will delete 2 lines
    • +
    • 3p → will paste the text 3 times
    • +
    • 100idesu [ESC] → will write “desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu”
    • +
    • . → Just after the last command will write again the 100 “desu”.
    • +
    • 3. → Will write 3 “desu” (and not 300, how clever).
    • +
    +
    +

    Stronger

    +

    Knowing how to move efficiently with vim is very important. Don’t skip this section.

    +
      +
    1. NG → Go to line N
    2. +
    3. gg → shortcut for 1G - go to the start of the file
    4. +
    5. G → Go to last line
    6. +
    7. Word moves:

      +
      +
        +
      1. w → go to the start of the following word,
      2. +
      3. e → go to the end of this word.
      4. +
      +

      By default, words are composed of letters and the underscore character. Let’s call a WORD a group of letter separated by blank characters. If you want to consider WORDS, then just use uppercase characters:

      +
        +
      1. W → go to the start of the following WORD,
      2. +
      3. E → go to the end of this WORD.
      4. +
      +

      Word moves example

      +
    8. +
    +

    Now let’s talk about very efficient moves:

    +
    +
      +
    • % : Go to the corresponding (, {, [.
    • +
    • * (resp. #) : go to next (resp. previous) occurrence of the word under the cursor
    • +
    +
    +

    Believe me, the last three commands are gold.

    +

    Faster

    +

    Remember about the importance of vi moves? Here is the reason. Most commands can be used using the following general format:

    +

    <start position><command><end position>

    +

    For example : 0y$ means

    +
      +
    • 0 → go to the beginning of this line
    • +
    • y → yank from here
    • +
    • $ → up to the end of this line
    • +
    +

    We also can do things like ye, yank from here to the end of the word. But also y2/foo yank up to the second occurrence of “foo”.

    +

    But what was true for y (yank), is also true for d (delete), v (visual select), gU (uppercase), gu (lowercase), etc…

    +

    4th Level – Vim Superpowers

    +

    With all preceding commands you should be comfortable using vim. But now, here are the killer features. Some of these features were the reason I started to use vim.

    +

    Move on current line: 0 ^ $ g_ f F t T , ;

    +
    +
      +
    • 0 → go to column 0
    • +
    • ^ → go to first character on the line
    • +
    • $ → go to the last column
    • +
    • g_ → go to the last character on the line
    • +
    • fa → go to next occurrence of the letter a on the line. , (resp. ;) will find the next (resp. previous) occurrence.
    • +
    • t, → go to just before the character ,.
    • +
    • 3fa → find the 3rd occurrence of a on this line.
    • +
    • F and T → like f and t but backward. Line moves
    • +
    +
    +

    A useful tip is: dt" → remove everything until the ".

    +

    Zone selection <action>a<object> or <action>i<object>

    +

    These command can only be used after an operator in visual mode. But they are very powerful. Their main pattern is:

    +

    <action>a<object> and <action>i<object>

    +

    Where action can be any action, for example, d (delete), y (yank), v (select in visual mode). The object can be: w a word, W a WORD (extended word), s a sentence, p a paragraph. But also, natural character such as ", ', ), }, ].

    +

    Suppose the cursor is on the first o of (map (+) ("foo")).

    +
    +
      +
    • vi" → will select foo.
    • +
    • va" → will select "foo".
    • +
    • vi) → will select "foo".
    • +
    • va) → will select ("foo").
    • +
    • v2i) → will select map (+) ("foo")
    • +
    • v2a) → will select (map (+) ("foo"))
    • +
    +
    +

    Text objects selection

    +

    Select rectangular blocks: <C-v>.

    +

    Rectangular blocks are very useful for commenting many lines of code. Typically: 0<C-v><C-d>I-- [ESC]

    +
      +
    • ^ → go to the first non-blank character of the line
    • +
    • <C-v> → Start block selection
    • +
    • <C-d> → move down (could also be jjj or %, etc…)
    • +
    • I-- [ESC] → write -- to comment each line
    • +
    +

    Rectangular blocks

    +

    Note: in Windows you might have to use <C-q> instead of <C-v> if your clipboard is not empty.

    +

    Completion: <C-n> and <C-p>.

    +

    In Insert mode, just type the start of a word, then type <C-p>, magic… Completion

    +

    Macros : qa do something q, @a, @@

    +

    qa record your actions in the register a. Then @a will replay the macro saved into the register a as if you typed it. @@ is a shortcut to replay the last executed macro.

    +
    +

    Example

    +

    On a line containing only the number 1, type this:

    +
      +
    • qaYp<C-a>q

    • +
    • qa start recording.
    • +
    • Yp duplicate this line.
    • +
    • <C-a> increment the number.
    • +
    • q stop recording.

    • +
    • @a → write 2 under the 1
    • +
    • @@ → write 3 under the 2
    • +
    • Now do 100@@ will create a list of increasing numbers until 103.

    • +
    +
    +

    Macros

    +

    Visual selection: v,V,<C-v>

    +

    We saw an example with <C-v>. There is also v and V. Once the selection has been made, you can:

    +
      +
    • J → join all the lines together.
    • +
    • < (resp. >) → indent to the left (resp. to the right).
    • +
    • = → auto indent
    • +
    +

    Autoindent

    +

    Add something at the end of all visually selected lines:

    +
      +
    • <C-v>
    • +
    • go to desired line (jjj or <C-d> or /pattern or % etc…)
    • +
    • $ go to the end of the line
    • +
    • A, write text, ESC.
    • +
    +

    Append to many lines

    +

    Splits: :split and vsplit.

    +

    These are the most important commands, but you should look at :help split.

    +
    +
      +
    • :split → create a split (:vsplit create a vertical split)
    • +
    • <C-w><dir> : where dir is any of hjkl or ←↓↑→ to change the split.
    • +
    • <C-w>_ (resp. <C-w>|) : maximise the size of the split (resp. vertical split)
    • +
    • <C-w>+ (resp. <C-w>-) : Grow (resp. shrink) split
    • +
    +
    +

    Split

    +

    Conclusion

    +

    That was 90% of the commands I use every day. I suggest that you learn no more than one or two new commands per day. After two to three weeks you’ll start to feel the power of vim in your hands.

    +

    Learning Vim is more a matter of training than plain memorization. Fortunately vim comes with some very good tools and excellent documentation. Run vimtutor until you are familiar with most basic commands. Also, you should read this page carefully: :help usr_02.txt.

    +

    Then, you will learn about !, folds, registers, plugins and many other features. Learn vim like you’d learn piano and all should be fine.

    + + + +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-08-25 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/Lost_Highway_Demistified/index.html b/Scratch/en/blog/Lost_Highway_Demistified/index.html new file mode 100644 index 0000000..557b238 --- /dev/null +++ b/Scratch/en/blog/Lost_Highway_Demistified/index.html @@ -0,0 +1,226 @@ + + + + + + YBlog - A try to demystify 'Lost Highway' + + + + + + + + + + + + + +
    + + +
    +

    A try to demystify 'Lost Highway'

    +
    +
    +
    +
    +

    Lost Highway

    +
    +…this movie must be watched knowing you’ll cannot resolve the solution. At his best you’ll can suggest an interpretation close to the one of David Lynch.
    I believe I had found a coherent interpretation which allow to follow the movie without being totally lost. I believed it can give the keys necessary to make its own idea of the movie… +
    + +

    Lost Higway is a really good movie. You keep watching it event it seem totally obscure. This is one of the strength of David Lynch.

    +

    The first time I watched Lost Highway, I was a bit lost. Here some of explanations of Lost Highway I found on the Internet:

    +
      +
    • Fred make a pact with the devil incarnated by the Mysterious Man,
    • +
    • Mysterious Man is a video camera,
    • +
    • Just the first part of the story is real. The rest is in the Fred’s imagination,
    • +
    +

    and I don’t speak about many point of view found in forums.

    +

    I finished to find two good site talking about this movie. But none of them still totally convinced me:

    +
      +
    • the first is mediacircus,
    • +
    • the second which state almost the same interpretation about the movie and explain with even more details is on jasonweb
    • +
    +

    Nonetheless, this movie must be watched knowing you’ll cannot resolve the solution. At his best you’ll can suggest an interpretation close to the one of David Lynch.

    +

    I believe I had found a coherent interpretation which allow to follow the movie without being totally lost. I believed it can give the keys necessary to make its own idea of the movie.

    +

    The Rorschach test

    +

    test de Rorschach

    +

    Like the protagonist, everybody see what he want to see in this movie. It is an invitation to think. Watch this movie is a little like watch a Rorschach’s test. What do we see in it? Everybody put its own personnality in the interpretation of the movie.

    +
      +
    • If you are mystic, you’ll see in the mysterious man a devil,
    • +
    • If you are more psychanalytics, you’ll see an inconscient part of the protagonist…
    • +
    +

    Generally, we stay in this movie and we fail explaining everything. There is almost always a point that don’t fit within the interpretation of the movie. This is why trying to find a unique good interpretation of this movie is a mistake.

    +

    Interprétation ≠ Explanation

    +

    I give an interpretation and not an explanation. Just to tell my vision of the movie should be very different from yours. There is certainly many coherent explanations.

    +

    I write this post because I believe I had found an interpretation which seems coherent for most of the movie.

    +

    kind: article menupriority: 1 published: 2009-08-04 title: A try to demystify ‘Lost Highway’ author: Yann Esposito authoruri: yannesposito.com subtitle: Movie’s keys —–

    +

    Movie’s keys

    +
    + + All is in Fred’s memory +
    + +

    In a first, it is clear for me, it is not a fantastic movie. If you follow this line, you’ll face many problem explaining some scenes.

    +

    My hypothesis is the movie describe the Fred’s representation of reality. Each of his tries to escape reality will fail.

    +

    Fred had commited an horrible act, a murder, and try to repair his memory to accepts it. He’ll then create alternative realities.

    +
      +
    • In a first time he kills his wife (Renee) because he believes she cheated at him.
    • +
    • In the second part, he’s weaker and will be manipulated by the blond equivalent of Renee to kill Dick Laurent.
    • +
    • In a third part, he kills Dick Laurent
    • +
    +

    Why this interpretation can be valid?

    +

    Because of the dialog at the begining of the movie. Cops ask Fred if he’s own a video camera:

    +
    +

    “Do you own a video camera?”
    “No, Fred hates them.”
    “I like to remember things my own way.”
    “What do you mean by that?”
    “How I remember them, not necessarily the way they happened.”

    +
    +

    Then, what we see is not reality but the Fred’s perception. Fred is the God of the reality we see. This is why some God/Devil interpretation of the movie works not so bad.

    +

    Who is the mysterious man?

    +

    +

    Who’s this mysterious man? He tells Fred it’s him who invited him in his house. He’s present at the party and in the house of Fred in the same time. Eyes wide open, looking everything Fred’s doing?

    +

    It’s a key of the movie. In my humble opinion, I believe it represents the bad part of Fred. Certainly jalousy. If I was catholic, I’ll said he’s Satan. He observe, film but don’t act. He helps Fred to kill Dick Laurent. Fred had let him enter and cannot let him go. As Iago of Shakespeare is imprisonned by its own jalousy. The Mysterious Man help Fred doing the acts of violence. It also force Fred to remember the reality.

    +

    When he makes love to his wife (Renee), he sees the face of the Mysterious Man instead of his wife’s face. In reality, it’s the same person for Fred. It should be her who’s the origin of his interior badness.

    + + + + + + + + + + + + + + + + + + + + + + + + +
    kind: article
    menupriority: 3
    published: 2009-08-04
    title: A try to demystify ‘Lost Highway’
    author: Yann Esposito
    authoruri: yannesposito.com
    subtitle: Who’s at the origin of the video tapes?
    +

    Who’s at the origin of the video tapes?

    +

    Certainly it’s the mysterious man (Fred himself) who makes them. Their reason should be:

    +
      +
    • Remember the reality to Fred. From Fred point-of-view, video tapes are the reality. He tries to forget reality. But, finally, the video tapes go to the end: the murder of his wife.
    • +
    • It may also be a reference to pornographic video tapes, made by Renee.

      +kind: article menupriority: 4 published: 2009-08-04 title: A try to demystify ‘Lost Highway’ author: Yann Esposito authoruri: yannesposito.com subtitle: What really happened? —–
    • +
    +

    What really happened?

    +

    There is many possibilities here. But we have many indices. Here is a supposition.

    +

    #1 Hypothesis

    +

    The protagonist is a garagist fallen in love with a porno actress. He believe the producer is the bad guy who go again his will. Then he kills Dick Laurent.

    +

    #2 Hypothesis

    +

    He was really married, he had killed his wife. The the remorse let him create an alternate self, which live in a kind of perfect world. But after the time pass, his obsession about the murder came again. And nobody could know if he had killed Andy or not.

    +

    which one then?

    +

    The second hypothesis seems better. We can make much more interpretation with it. It explain in most part the strange phone call from Dick Laurent to Pete. But the first hypothesis remain coherent. And, we should probably make an in depth explanantion using the first hypothesis. And I’m not sure it would be better.

    +

    One of the strength of this movie is to understand there is many other coherent hypothesis. It is an expression of the Rashomon effect. Many different persons could describe in a coherent manner what they saw. But each description contradicts the others.

    +
    +

    Conclusion

    +

    There is much to tell about this movie. But I believe I put all essential keys here. It is a proof this movie is not a random one.

    +

    I believe it is essential to remember the “test of Rorschach effet” when watching this movie.

    +

    I’d like to know or opinion ; is my interpration wrong?

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2009-08-04 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/Password-Management/index.html b/Scratch/en/blog/Password-Management/index.html new file mode 100644 index 0000000..491498e --- /dev/null +++ b/Scratch/en/blog/Password-Management/index.html @@ -0,0 +1,182 @@ + + + + + + YBlog - 40 character's passwords + + + + + + + + + + + + + +
    + + +
    +

    40 character's passwords

    +
    +
    +
    +
    +

    Title image

    +
    + +

    tl;dr: How I manage safely my password with success for some years now.
    sha1( password + domain_name )
    I memorize only one password. I use a different password on all website.

    +
    + +

    Disclamer, this is an unashamed attempt to make you download my iPhone app ;-). You’re always here? Even if you won’t download my app, you should read more. My method doesn’t necessitate my app. It is both safe and easy to use everyday.

    +

    If you just want to use the tools without searching to understand why it is safe, just jump at the end of this article by clicking here.

    +

    Why you should use a Password Manager?

    +
    +

    Even paranoid could have ennemies.

    +
    +

    Imagine you find a really good password. You use it on GMail, Amazon, PayPal, Twitter, Facebook… One day you see a nice online game you want to try. They ask you your email and a password. Some week passes, and the host machine of this online game is hacked. Your mail and password is now in bad hands. Unfortunately for you, you use the same password everywhere. Then, the attacker can simply try your password everywhere. On PayPal for example.

    +

    Well now, how could we fix that?

    +

    Which methodology?

    +
    +

    the good, the bad & the ugly

    +
    +

    The mostly used method is to remember a subset of different passwords. In the best cases, your remember about 13 password. Some strong, some weak.

    +

    What to do if you use more online services than your memory can handle?

    +

    A bad solution would be to chose passwords like this:

    +
      +
    • twitter: P45sW0r|)Twitter
    • +
    • gmail: P45sW0r|)gmail
    • +
    • badonlinegame: P45sW0r|)badonlinegame
    • +
    +

    Unfortunately, if someone get your password on badonlinegame, he could easily find your other passwords. Of course you can imagine some better transformation. But it is hard to find a very good one.

    +

    Fortunately, there exists functions which handle exactly this problem. Hash Function. Knowing the result of a hash function, it is difficult to know what was their input. For example:

    +
    hash("P45sW0r|)") = 9f00fd5dbba232b7c03afd2b62b5fce5cdc7df63
    +

    If someone has 9f00fd5dbba232b7c03afd2b62b5fce5cdc7df63, he will have hard time to recover P45sW0r|).

    +

    Let choose SHA1 as hash function. Now the password for any website should of the form:

    +

    sha1( master_password + domain_name ) ~~~~~~

    +

    Where:

    +
      +
    • master_password is your unique master password,
    • +
    • domain_name is the domain name of the website you want the password for,
    • +
    +
    +

    But what about some website constraint? For example regarding the length of the password? What to do if you want to change your password? What to do if you want number or special characters? This is why, for each website I need some other parameters:

    +
      +
    • the login name
    • +
    • the password’s length,
    • +
    • the password number (in order to change it),
    • +
    • The output format: hexadecimal or base64.
    • +
    +

    In practice?

    +

    Depending on my situation here are the tools I made & use:

    + +

    My password are at a copy/paste on all environment I use. I have some services for which I have password of 40 characters. Now I use 10 character for most of my passwords. Further more using shorter password make it even harder for an attaquer to retrieve my master password.

    +

    I would be happy to hear your thoughts on using this methodology.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-05-18 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/SVG-and-m4-fractals/code/yesodlogo.m4 b/Scratch/en/blog/SVG-and-m4-fractals/code/yesodlogo.m4 new file mode 100644 index 0000000..39ab18c --- /dev/null +++ b/Scratch/en/blog/SVG-and-m4-fractals/code/yesodlogo.m4 @@ -0,0 +1,43 @@ + + + + + + λ + + + esod + + + + YTRANSCOMPLETE(1,0) + YTRANSCOMPLETE(2,1) + YTRANSCOMPLETE(3,2) + YTRANSCOMPLETE(4,3) + YTRANSCOMPLETE(5,4) + diff --git a/Scratch/en/blog/SVG-and-m4-fractals/index.html b/Scratch/en/blog/SVG-and-m4-fractals/index.html new file mode 100644 index 0000000..0402415 --- /dev/null +++ b/Scratch/en/blog/SVG-and-m4-fractals/index.html @@ -0,0 +1,250 @@ + + + + + + YBlog - Increase the power of deficient languages. + + + + + + + + + + + + + +
    + + +
    +

    Increase the power of deficient languages.

    +
    +
    +
    +
    +

    Yesod logo made in SVG and m4

    +
    + +

    tl;dr: How to use m4 to increase the power of deficient languages. Two examples: improve xslt syntax and make fractal with svg.

    +
    + +

    xml was a very nice idea about structuring data. Some people where so enthusiastic about xml they saw it everywhere. The idea was: the future is xml. Then some believed it would be a good idea to invent many xml compatible format and even programming languages with xml syntax.

    +

    Happy! Happy! Joy! Joy!

    +

    Unfortunately, xml was made to transfert structured data. Not a format a human should see or edit directly. The sad reality is xml syntax is simply verbose and ugly. Most of the time it shouldn’t be a problem, as nobody should see it. In a perfect nice world, we should never deal directly with xml but only use software which deal with it for us. Guess what? Our world isn’t perfect. Too sad, a bunch of developer have to deal directly with this ugly xml.

    +

    Unfortunately xml isn’t the only case of misused format I know. You have many format for which it would be very nice to add variables, loops, functions…

    +

    If like me you hate with passion xslt or writing xml, I will show you how you could deal with this bad format or language.

    +

    The xslt Example

    +

    Let’s start by the worst case of misused xml I know: xslt. Any developer who had to deal with xslt know how horrible it is.

    +

    In order to reduce the verbosity of such a bad languages, there is a way. m4. Yes, the preprocessor you use when you program in C and C++.

    +

    Here are some example:

    +
      +
    • Variable, instead of writing the natural myvar = value, here is the xslt way of doing this:
    • +
    +
    <xsl:variable name="myvar" select="value"/>
    +
      +
    • Printing something. Instead of print "Hello world!" here is the xslt equivalent:
    • +
    +
    <xsl:text 
    +    disable-output-escaping="yes"><![CDATA[Hello world!
    +]]></xsl:text>
    +
      +
    • printing the value of a variable, instead of print myvar the xslt is:
    • +
    +
    <xslt:value-of select="myvar"/>
    +
      +
    • Just try to imagine how verbose it is to declare a function with this language.
    • +
    +

    The cure (m4 to the rescue)

    +
    <?xml version="1.0" standalone="yes"?> <!-- YES its <span class="sc">xml</span> -->
    +<!-- ← start a comment, then write some m4 directives:
    +
    +define(`ydef',`<xsl:variable name="$1" select="$2"/>')
    +define(`yprint',`<xsl:text disable-output-escaping="yes"><![CDATA[$1]]></xsl:text>')
    +define(`yshow',`<xsl:value-of select="$1"/>')
    +
    +-->
    +<!-- Yes, <span class="sc">xml</span> sucks to be read -->
    +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    +<!-- And it sucks even more to edit -->
    +<xsl:template match="/">
    +    ydef(myvar,value)
    +    yprint(Hello world!)
    +    yshow(myvar)
    +</xsl:template>
    +

    Now just compile this file:

    +
    m4 myfile.m4 > myfile.xslt
    +

    Profit! Now xslt is more readable and easier to edit!

    +

    The cool part: Fractals!

    +

    svg is an xml format used to represent vector graphics, it even support animations. At its beginning some people believed it would be the new Flash. Apparently, it will be more canvas + js.

    +

    Let me show you the result:

    +

    Yesod logo made in SVG and m4 Click to view directly the svg. It might slow down your computers if you have an old one.

    +

    The positionning of the “esod” text with regards to the reversed “λ” was done by changing position in firebug. I didn’t had to manually regenerate to test.

    +

    Making such a fractal is mostly:

    +
      +
    1. take a root element
    2. +
    3. duplicate and transform it (scaling, translating, rotate)
    4. +
    5. the result is a sub new element.
    6. +
    7. repeat from 2 but by taking the sub new element as new root.
    8. +
    9. Stop when recursion is deep enough.
    10. +
    +

    If I had to do this for each step, I had make a lot of copy/paste in my svg, because the transformation is always the same, but I cannot say, use transformation named “titi”. Then instead of manually copying some xml, I used m4

    +

    and here is the commented code:

    +
    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    +<!--
    +     M4 Macros
    +define(`YTRANSFORMONE', `scale(.43) translate(-120,-69) rotate(-10)')
    +define(`YTRANSFORMTWO', `scale(.43) translate(-9,-67.5) rotate(10)')
    +define(`YTRANSFORMTHREE', `scale(.43) translate(53,41) rotate(120)')
    +define(`YGENTRANSFORM', `translate(364,274) scale(3)')
    +define(`YTRANSCOMPLETE', `
    +    <g id="level_$1">
    +        <use style="opacity: .8" transform="YTRANSFORMONE" xlink:href="#level_$2" />
    +        <use style="opacity: .8" transform="YTRANSFORMTWO" xlink:href="#level_$2" />
    +        <use style="opacity: .8" transform="YTRANSFORMTHREE" xlink:href="#level_$2" />
    +    </g>
    +    <use transform="YGENTRANSFORM" xlink:href="#level_$1" />
    +')
    + -->
    +<svg 
    +    xmlns="http://www.w3.org/2000/svg" 
    +    xmlns:xlink="http://www.w3.org/1999/xlink"
    +    x="64" y="64" width="512" height="512" viewBox="64 64 512 512"
    +    id="svg2" version="1.1">
    +    <g id="level_0"> <!-- some group, if I want to add other elements -->
    +        <!-- the text "λ" -->
    +        <text id="lambda" 
    +            fill="#333" style="font-family:Ubuntu; font-size: 100px"
    +            transform="rotate(180)">λ</text>
    +    </g>
    +    <!-- the text "esod" -->
    +    <text 
    +        fill="#333" 
    +        style="font-family:Ubuntu; font-size: 28px; letter-spacing: -0.10em" 
    +        x="-17.3" 
    +        y="69" 
    +        transform="YGENTRANSFORM">esod</text>
    +    <!-- ROOT ELEMENT -->
    +    <use transform="YGENTRANSFORM" xlink:href="#level_0" />
    +
    +    YTRANSCOMPLETE(1,0) <!-- First recursion -->
    +    YTRANSCOMPLETE(2,1) <!-- deeper -->
    +    YTRANSCOMPLETE(3,2) <!-- deeper -->
    +    YTRANSCOMPLETE(4,3) <!-- even deeper -->
    +    YTRANSCOMPLETE(5,4) <!-- Five level seems enough -->
    +</svg>
    +

    and I compiled it to svg and then to png with:

    +
    m4 yesodlogo.m4 > yesodlogo.svg && convert yesodlogo.svg yesodlogo.png
    +

    The main λ is duplicated 3 times. Each transformation is named by: YTRANSFORMONE, YTRANSFORMTWO and YTRANSFORMTHREE.

    +

    Each transformation is just a similarity (translate + rotation + scale).

    +

    Once fixed, we should now simply copy and repeat for each new level.

    +

    Now it is time to talk about where the magic occurs: YTRANSCOMPLETE. This macro takes two arguments. The current depth and the preceding one. It duplicates using the three transformations the preceding level.

    +
      +
    • At level 0 there is only one λ,
    • +
    • at level 1 there is 3 λ,
    • +
    • at level 2 there is 9 λ
    • +
    • etc…
    • +
    +

    At the final 5th level there is 35=243 λ. All level combined have 36-1 / 2 = 364 λ.

    +

    I could preview the final result easily. Without the macro system, I would have to make 5 copy/paste + modifications for each try.

    +

    Conclusion

    +

    It was fun to make a fractal in svg, but the interesting part is how to augment the power of a language using this preprocessor method. I used the xslt trick at work for example. I also used it to make include inside obscure format. If all you want is to generate a minimal static website withou using nanoc, jekyll or hakyll (ther are plenty other alternatives). You can consider using m4 to generate your html instead of copy/paste the menu and the footer, or using AJAX.

    +

    Another usage I thouhgt about is to use m4 to organize languages such as brainfuck.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-10-20 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/Typography-and-the-Web/index.html b/Scratch/en/blog/Typography-and-the-Web/index.html new file mode 100644 index 0000000..c067b2c --- /dev/null +++ b/Scratch/en/blog/Typography-and-the-Web/index.html @@ -0,0 +1,175 @@ + + + + + + YBlog - Typography and the Web + + + + + + + + + + + + + +
    + + +
    +

    Typography and the Web

    +
    +
    +
    +
    +

    +
    + +

    tl;dr: Web typography sucks and we’ll have to wait forever before it will be fixed.

    +
    + +

    I stumbled upon open typography. Their main message is:

    +
    +

    «There is no reason to wait for browser development to catch up. We can all create better web typography ourselves, today.»

    +
    +

    As somebody who tried to make my website using some nice typography features and in particular ligatures, I believe this is wrong.

    +

    I already made an automatic system which will detect and replace text by their ligatures in my blog. But this I never published this on the web and this is why.

    +

    First, what is a ligatures?

    +

    +

    What is the problem between the Web and ligatures? The first one is: you cannot search them. For example, try to search the word “first”:

    +
      +
    • first ← No ligature, no problem1
    • +
    • r ← ligature nice but unsearchable
    • +
    +

    The second one is the rendering, for example, try to use a ligature character with small caps:

    +
      +
    • first
    • +
    • r
    • +
    +

    Here is a screenshot of what I see:

    +

    +

    The browser isn’t able to understand that the ligature character “” should render as fi when rendered in small caps. And one part of the problem is you should choose to display a character in small caps using css.

    +

    This way, how could you use a ligature Unicode character on a site on which you could change the css?

    +

    Let’s compare to LaTeX.

    +

    +

    If you take attention to detail, you’ll see the first “first” contains a ligature. Of course the second render nicely. The code I used were:

    +
    \item first
    +\item {\sc first}
    +

    LaTeX was intelligent enough to create himself the ligatures when needed.

    +

    The “” ligature is rare and not rendered in LaTeX by default. But if you want you could also render rare ligature using XƎLaTeX:

    +

    XeLaTeX ligatures

    +

    I took this image from the excellent article of Dario Taraborelli.

    +

    Clearly fix the rendering of ligature in a browser is a difficult task. Simply imagine the number of strange little exceptions:

    +
      +
    • The text is rendered in small caps, I cannot use ligature.
    • +
    • The current word contains a ligature unicode character, I should search for ligature in this one.
    • +
    • The current font does not defined the ligature unicode character, we shouldn’t use it, etc
    • +
    • A javascript command changed the CSS, I should verify if I had to revert the insertion of ligatures characters
    • +
    • etc…
    • +
    +

    Nonetheless if someone has a solution, I would be happy to hear about it.

    +
    +
    +
      +
    1. In fact, you might see a ligature and the search works because I now use some CSS ninja skills: text-rendering: optimizelegibility. But it also works because I use the right font; Computer Modern. Steal my CSS at will.

    2. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2012-02-02 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/Yesod-excellent-ideas/index.html b/Scratch/en/blog/Yesod-excellent-ideas/index.html new file mode 100644 index 0000000..c6bddb8 --- /dev/null +++ b/Scratch/en/blog/Yesod-excellent-ideas/index.html @@ -0,0 +1,195 @@ + + + + + + YBlog - Yesod excellent ideas + + + + + + + + + + + + + +
    + + +
    +

    Yesod excellent ideas

    +
    +
    +
    +
    +

    Title image

    +
    + +

    tl;dr:

    +

    Yesod is a framework which has recently matured to the point where you should consider using it. Before telling you why you should learn Haskell and use Yesod, I will illustrate the many features Yesod introduces which are missing in other frameworks.

    +
    + +

    Type safety

    +

    Let’s start by an obligatory link from xkcd:

    +
    +SQL injection by a mom

    SQL injection by a mom

    +
    +

    When you create a web application, a lot of time is spent dealing with strings. Strings for URL, HTML, JavaScript, CSS, SQL, etc… To prevent malicious usage you have to protect each strings to be sure, no script will pass from one point to another. Suppose a user enter this user name:

    +
    Newton<script>alert("An apple fall")</script>
    +

    You must transform each < into &lt;. Without this transformation alert will appear each time you try to display this user name. Safe types associate with each string what kind of string it is. Is it a string for URL? For javascript? For HTML? And the right protection is made by default to prevent problems.

    +

    Yesod does its best to handle cross scripting issues. Both between the client and the server and between the server and your DB. Here is an example:

    +

    Go to the other page ~~~~~~

    +

    As AnotherPageR is of type URL and it could not contains something nefarious. It will be an URL safe. Not something like:

    +
    falselink"><script> bad_code(); </script><a href="pipo
    +

    Widgets

    +

    Yesod’s widgets are different from javascript widget. For yesod, widgets are sets of small parts of a web application. If you want to use many widgets in a same page yesod do the work. Some examples of widget are:

    +
      +
    • the footer of a webpage,
    • +
    • the header of a webpage with a menu,
    • +
    • a button which appears only when scrolling down,
    • +
    • etc…
    • +
    +

    For each of this part, you might need,

    +
      +
    • a bit of HTML,
    • +
    • a bit of CSS and
    • +
    • a bit of javascript.
    • +
    +

    Some in the header, some in the body.

    +

    You can declare a widget as this (note I use a very high meta-language):

    +
    htmlheader = ...
    +cssheader = ...
    +javascriptheader = ...
    +htmlbody = ...
    +

    The real syntax is:

    +
    toWidgetHeader cassiusFile "button.cassius"
    +toWidgetHeader juliusFile "button.julius"
    +toWidget       hamletFile "buttonTemplate.hamlet"
    +

    Note the awesome Shakespearean inspired name convention. Another good reason to use yesod.

    +
      +
    • Cassius & Lucius of CSS (a lot similar to SASS and SCSS),
    • +
    • Julius for JavaScript (note a CoffeeScript is somewhere in the source of yesod),
    • +
    • Hamlet for HTML (similar to haml)
    • +
    +

    And when your page render, yesod makes it easy to render everything nicely:

    +
    myBigWidget =  menuWidget >> contentWidget >> footerWidget
    +

    Furthermore, if you use say 10 widgets each with a bit of CSS, yesod will create a unique and compressed CSS file. Except if you expressed a need to change the header by using different CSS.

    +

    This is just awesome!

    +

    Optimized routing

    +

    In standard routing system you have for each entry a couple: regexp → handler

    +

    The only way to discover the right rules is to match each regexp to the current URL. Then you can see behaviour such as, if you change the order of the rules you can lose or win time.

    +

    On the other hand yesod compiles the routes. Therefore it can optimize it. Of course two routes must not interfere.

    +
    /blog/2003  Date2003R
    +/blog/$DATE DateR
    +

    is invalid by default (you can make it valid, but I don’t think it is a good idea).

    +

    You’d better

    +
    /blog/$DATE DateR
    +

    and test if date = 2003 inside the handler.

    +

    Why yesod?

    +
      +
    1. Speed. This is just astounding. Look at this and then to this.
    2. +
    3. Haskell. This is certainly hard to learn but also incredibly awesome. If you want to make you a favor. Just learn Haskell. It will be difficult, far more than you can imagine. It is very different from all other languages I used. But it will blow your mind and learn you a bunch of new programming concepts.
    4. +
    5. Good ideas, excellent community. I follow yesod from some month now and the speed at which the project progress is incredible.
    6. +
    +

    If you are a haskeller, I believe you shouldn’t fear the special syntax imposed by the standard yesod way of doing things. Just try it more than the firsts basic tutorials.

    +

    Until here I believe it goes in the right direction. Even if I believe the real future is by generating HTML pages from the client (using javascript) and server limited to serve JSON (or XML, or any object representation system).

    +

    To conclude, Yesod is awesome. Just overcome the difficulties about learning a bit of haskell and try it!

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-10-04 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/en/blog/Yesod-tutorial-for-newbies/code/Echo.hs b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/Echo.hs new file mode 100644 index 0000000..4ba6b60 --- /dev/null +++ b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/Echo.hs @@ -0,0 +1,8 @@ +module Handler.Echo where + +import Import + +getEchoR :: Text -> Handler RepHtml +getEchoR theText = do + defaultLayout $ do + [whamlet|

    #{theText}|] diff --git a/Scratch/en/blog/Yesod-tutorial-for-newbies/code/Mirror.hs b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/Mirror.hs new file mode 100644 index 0000000..c7a006a --- /dev/null +++ b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/Mirror.hs @@ -0,0 +1,15 @@ +module Handler.Mirror where + +import Import +import qualified Data.Text as T + +getMirrorR :: Handler RepHtml +getMirrorR = do + defaultLayout $ do + $(widgetFile "mirror") + +postMirrorR :: Handler RepHtml +postMirrorR = do + postedText <- runInputPost $ ireq textField "content" + defaultLayout $ do + $(widgetFile "posted") diff --git a/Scratch/en/blog/Yesod-tutorial-for-newbies/code/article.hamlet b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/article.hamlet new file mode 100644 index 0000000..6c32d42 --- /dev/null +++ b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/article.hamlet @@ -0,0 +1,2 @@ +

    #{articleTitle article} +
    #{articleContent article} diff --git a/Scratch/en/blog/Yesod-tutorial-for-newbies/code/articles.hamlet b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/articles.hamlet new file mode 100644 index 0000000..9ef45ac --- /dev/null +++ b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/articles.hamlet @@ -0,0 +1,15 @@ +

    Articles +$if null articles + -- Show a standard message if there is no article +

    There are no articles in the blog +$else + -- Show the list of articles +

      + $forall Entity articleId article <- articles +
    • + #{articleTitle article} +
      +
      + ^{articleWidget} +
      + diff --git a/Scratch/en/blog/Yesod-tutorial-for-newbies/code/default-layout.lucius b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/default-layout.lucius new file mode 100644 index 0000000..b576178 --- /dev/null +++ b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/default-layout.lucius @@ -0,0 +1,23 @@ +body { + font-family: Helvetica, sans-serif; + font-size: 18px; } +#main { + padding: 1em; + border: #CCC solid 2px; + border-radius: 5px; + margin: 1em; + width: 37em; + margin: 1em auto; + background: #F2F2F2; + line-height: 1.5em; + color: #333; } +.required { margin: 1em 0; } +.optional { margin: 1em 0; } +label { width: 8em; display: inline-block; } +input, textarea { background: #FAFAFA} +textarea { width: 27em; height: 9em;} +ul { list-style: square; } +a { color: #A56; } +a:hover { color: #C58; } +a:active { color: #C58; } +a:visited { color: #943; } diff --git a/Scratch/en/blog/Yesod-tutorial-for-newbies/code/echo.hamlet b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/echo.hamlet new file mode 100644 index 0000000..38745cf --- /dev/null +++ b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/echo.hamlet @@ -0,0 +1 @@ +

      #{theText} diff --git a/Scratch/en/blog/Yesod-tutorial-for-newbies/code/mirror.hamlet b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/mirror.hamlet new file mode 100644 index 0000000..c81d170 --- /dev/null +++ b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/mirror.hamlet @@ -0,0 +1,4 @@ +

      Enter your text + + + diff --git a/Scratch/en/blog/Yesod-tutorial-for-newbies/code/posted.hamlet b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/posted.hamlet new file mode 100644 index 0000000..bf5ca7a --- /dev/null +++ b/Scratch/en/blog/Yesod-tutorial-for-newbies/code/posted.hamlet @@ -0,0 +1,4 @@ +

      You've just posted +

      #{postedText}#{T.reverse postedText} +


      +

      Get back diff --git a/Scratch/en/blog/Yesod-tutorial-for-newbies/index.html b/Scratch/en/blog/Yesod-tutorial-for-newbies/index.html new file mode 100644 index 0000000..1e0b859 --- /dev/null +++ b/Scratch/en/blog/Yesod-tutorial-for-newbies/index.html @@ -0,0 +1,526 @@ + + + + + + YBlog - Haskell web programming + + + + + + + + + + + + + +

      + + +
      +

      Haskell web programming

      +
      +
      +
      +
      +

      Neo Flying at warp speed

      +
      + +

      update: updated for yesod 0.10

      +

      tl;dr: A simple yesod tutorial. Yesod is a Haskell web framework. You shouldn’t need to know Haskell.

      +
      +
      +Table of content +
      + +
        +
      • Table of Content (generated) {:toc}
      • +
      +
      +
      + +

      Why Haskell?

      +

      Impressive Haskell Benchmark

      +

      Its efficiency (see [Snap Benchmark][snapbench] & Warp Benchmark[^benchmarkdigression]). Haskell is an order of magnitude faster than interpreted languages like [Ruby][haskellvsruby] and [Python][haskellvspython][^speeddigression].

      +

      Haskell is a high level language and make it harder to shoot you in the foot than C, C++ or Java for example. One of the best property of Haskell being:

      +
      +

      “If your program compile it will be very close to what the programmer intended”.

      +
      +

      Haskell web frameworks handle parallel tasks perfectly. For example even better than node.js[^nodejstroll].

      +

      Thousands of Agent Smith

      +

      From the pure technical point of view, Haskell seems to be the perfect web development tool. Weaknesses of Haskell certainly won’t be technical:

      +
        +
      • Hard to grasp Haskell
      • +
      • Hard to find a Haskell programmer
      • +
      • The Haskell community is smaller than the community for /.*/
      • +
      • There is no heroku for Haskell (even if Greg Weber did it, it was more a workaround).
      • +
      +

      I won’t say these are not important drawbacks. But, with Haskell your web application will have both properties to absorb an impressive number of parallel request securely and to adapt to change.

      +

      Actually there are three main Haskell web frameworks:

      +
        +
      1. Happstack
      2. +
      3. Snap
      4. +
      5. Yesod
      6. +
      +

      I don’t think there is a real winner between these three framework. The choice I made for yesod is highly subjective. I just lurked a bit and tried some tutorials. I had the feeling yesod make a better job at helping newcomers. Furthermore, apparently the yesod team seems the most active. Of course I might be wrong since it is a matter of feeling.

      +

      1. Draw some circles. 2. Draw the rest of the fucking owl

      +

      Why did I write this article? The yesod documentation and particularly the book are excellent. But I missed an intermediate tutorial. This tutorial won’t explain all details. I tried to give a step by step of how to start from a five minute tutorial to an almost production ready architecture. Furthermore explaining something to others is a great way to learn. If you are used to Haskell and Yesod, this tutorial won’t learn you much. If you are completely new to Haskell and Yesod it might hopefully helps you. Also if you find yourself too confused by the syntax, it might helps to read this article

      +

      During this tutorial you’ll install, initialize and configure your first yesod project. Then there is a very minimal 5 minutes yesod tutorial to heat up and verify the awesomeness of yesod. Then we will clean up the 5 minutes tutorial to use some “best practices”. Finally there will be a more standard real world example; a minimal blog system.

      +

      [snapbench]: http://snapframework.com/blog/2010/11/17/snap-0.3-benchmarks [^benchmarkdigression]: One can argue these benchmark contains many problems. But the benchmarks are just here to give an order of idea. Mainly Haskell is very fast. [^speeddigression]: Generally high level Haskell is slower than C, but low level Haskell is equivalent to C speed. It means that even if you can easily link C code with Haskell, this is not needed to reach the same speed. Furthermore writing a web service in C/C++ seems to be a very bad idea. You can take a look at a discussion on HN about this. [^nodejstroll]: If you are curious, you can search about the Fibonacci node.js troll. Without any tweaking, Haskell handled this problem perfectly. I tested it myself using yesod instead of Snap. [haskellvsruby]: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=ghc&lang2=yarv [haskellvspython]: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=ghc&lang2=python3

      +

      Before the real start

      +

      Install

      +

      The recommended way to install Haskell is to download the Haskell Platform.

      +

      Once done, you need to install yesod. Open a terminal session and do:

      +
      ~ cabal update
      +~ cabal install yesod cabal-dev
      +

      There are few steps but it should take some time to finish.

      +

      Initialize

      +

      You are now ready to initialize your first yesod project. Open a terminal and type:

      +
      ~ yesod init
      +

      Enter your name, choose yosog for the project name and enter Yosog for the name of the Foundation. Finally choose sqlite. Now, start the development cycle:

      +
      ~ cd yosog
      +~ cabal-dev install && yesod --dev devel
      +

      This will compile the entire project. Be patient it could take a while the first time. Once finished a server is launched and you could visit it by clicking this link:

      +

      http://localhost:3000

      +

      Congratulation! Yesod works!

      +
      + +

      Note: if something is messed up use the following command line inside the project directory.

      +
      \rm -rf dist/* ; cabal-dev install && yesod --dev devel
      +
      + +

      Until the end of the tutorial, use another terminal and let this one open in a corner to see what occurs.

      +

      Configure git

      +
      +

      Of course this step is not mandatory for the tutorial but it is a good practice.

      +
      +

      Copy this .gitignore file into the yosog folder.

      +
      cabal-dev
      +dist
      +.static-cache
      +static/tmp
      +*.sqlite3
      +

      Then initialize your git repository:

      +
      ~ git init .
      +~ git add .
      +~ git commit -a -m "Initial yesod commit"
      +

      We are almost ready to start.

      +

      Some last minute words

      +

      Up until here, we have a directory containing a bunch of files and a local web server listening the port 3000. If we modify a file inside this directory, yesod should try to recompile as fast as possible the site. Instead of explaining the role of every file, let’s focus only on the important files/directories for this tutorial:

      +
        +
      1. config/routes
      2. +
      3. Handler/
      4. +
      5. templates/
      6. +
      7. config/models
      8. +
      +

      Obviously:

      +

      config/routes | is where you’ll configure the map %url → Code. |
      Handler/ | contains the files that will contain the code called when a %url is accessed. |
      templates/ | contains html, js and css templates. |
      config/models | is where you’ll configure the persistent objects (database tables). |

      +

      During this tutorial we’ll modify other files as well, but we won’t explore them in detail.

      +

      Also note, shell commands are executed in the root directory of your project instead specified otherwise.

      +

      We are now ready to start!

      +

      Echo

      +

      To verify the quality of the security of the yesod framework, let’s make a minimal echo application.

      +
      +

      Goal:

      +

      Make a server that when accessed /echo/[some text] should return a web page containing “some text” inside an h1 bloc.

      +
      +

      In a first time, we must declare the %url of the form /echo/... are meaningful. Let’s take a look at the file config/routes:

      +
      +/static StaticR Static getStatic
      +/auth   AuthR   Auth   getAuth
      +
      +/favicon.ico FaviconR GET
      +/robots.txt RobotsR GET
      +
      +/ HomeR GET
      +
      + +

      We want to add a route of the form /echo/[anything] somehow and do some action with this. Add the following:

      +
      +/echo/#String EchoR GET
      +
      + +

      This line contains three elements: the %url pattern, a handler name, an %http method. I am not particularly fan of the big R notation but this is the standard convention.

      +

      If you save config/routes, you should see your terminal in which you launched yesod devel activate and certainly displaying an error message.

      +
      +Application.hs:31:1: Not in scope: `getEchoR'
      +
      + +

      Why? Simply because we didn’t written the code for the handler EchoR. Edit the file Handler/Home.hs and append this:

      +
      getEchoR :: String -> Handler RepHtml
      +getEchoR theText = do
      +    defaultLayout $ do
      +        [whamlet|<h1>#{theText}|]
      +

      Don’t worry if you find all of this a bit cryptic. In short it just declare a function named getEchoR with one argument (theText) of type String. When this function is called, it return a Handler RepHtml whatever it is. But mainly this will encapsulate our expected result inside an html text.

      +

      After saving the file, you should see yesod recompile the application. When the compilation is finished you’ll see the message: Starting devel application.

      +

      Now you can visit: http://localhost:3000/echo/Yesod%20rocks!

      +

      TADA! It works!

      +

      Bulletproof?

      +

      Neo stops a myriad of bullets

      +

      Even this extremely minimal web application has some impressive properties. For exemple, imagine an attacker entering this %url:

      +[http://localhost:3000/echo/<a>I'm <script>alert("Bad!");](http://localhost:3000/echo/I’m + +

      " %>

      +

      The special characters are protected for us. A malicious user could not hide some bad script inside.

      +

      This behavior is a direct consequence of type safety. The %url string is put inside a %url type. Then the interesting part in the %url is put inside a String type. To pass from %url type to String type some transformation are made. For example, replace all “%20” by space characters. Then to show the String inside an html document, the string is put inside an html type. Some transformations occurs like replace “<” by “&lt;”. Thanks to yesod, this tedious job is done for us.

      +
      "http://localhost:3000/echo/some%20text<a>" :: URL
      +                    ↓
      +              "some text<a>"                 :: String
      +                    ↓
      +          "some text &amp;lt;a&amp;gt;"             :: Html 
      +

      Yesod is not only fast, it helps us to remain secure. It protects us from many common errors in other paradigms. Yes, I am looking at you PHP!

      +

      Cleaning up

      +

      Even this very minimal example should be enhanced. We will clean up many details:

      +
      +

      Use a better css

      +

      It is nice to note, the default template is based on %html5 boilerplate. Let’s change the default css. Add a file named default-layout.lucius inside the templates/ directory containing:

      +
      body {
      +    font-family: Helvetica, sans-serif; 
      +    font-size: 18px; }
      +#main {
      +    padding: 1em;
      +    border: #CCC solid 2px;
      +    border-radius: 5px;
      +    margin: 1em;
      +    width: 37em;
      +    margin: 1em auto;
      +    background: #F2F2F2;
      +    line-height: 1.5em;
      +    color: #333; }
      +.required { margin: 1em 0; }
      +.optional { margin: 1em 0; }
      +label { width: 8em; display: inline-block; }
      +input, textarea { background: #FAFAFA}
      +textarea { width: 27em; height: 9em;}
      +ul { list-style: square; }
      +a { color: #A56; }
      +a:hover { color: #C58; }
      +a:active { color: #C58; }
      +a:visited { color: #943; }
      +

      Personally I would prefer if such a minimal css was put with the scaffolding tool. I am sure somebody already made such a minimal css which give the impression the browser handle correctly html without any style applied to it. But I digress.

      +

      Separate Handlers

      +

      Generally you don’t want to have all your code inside a unique file. This is why we will separate our handlers. In a first time create a new file Handler/Echo.hs containing:

      +
      module Handler.Echo where
      +
      +import Import
      +
      +getEchoR :: String -> Handler RepHtml
      +getEchoR theText = do
      +    defaultLayout $ do
      +        [whamlet|<h1>#{theText}|]
      +

      Do not forget to remove the getEchoR function inside Handler/Home.hs.

      +

      We must declare this new file intoyosog.cabal. Just after Handler.Home, add:

      +
      +    Handler.Echo
      +
      + +

      We must also declare this new Handler module inside Application.hs. Just after the “import Handler.Home”, add:

      +
      import Handler.Echo
      +

      This is it.

      +

      ps: I am sure not so far in the future we could simply write yesod add-handler Echo to declare it and create a new handler file.

      +

      Data.Text

      +

      It is a good practice to use Data.Text instead of String.

      +

      To declare it, add this import directive to Foundation.hs (just after the last one):

      +
      import Data.Text
      +

      We have to modify config/routes and our handler accordingly. Replace #String by #Text in config/routes:

      +
      +/echo/#Text EchoR GET
      +
      + +

      And do the same in Handler/Echo.hs:

      +
      module Handler.Echo where
      +
      +import Import
      +
      +getEchoR :: Text -> Handler RepHtml
      +getEchoR theText = do
      +    defaultLayout $ do
      +        [whamlet|<h1>#{theText}|]
      +

      Use templates

      +

      Some html (more precisely hamlet) is written directly inside our handler. We should put this part inside another file. Create the new file templates/echo.hamlet containing:

      +
      <h1> #{theText}
      +

      and modify the handler Handler/Echo.hs:

      +
      getEchoR :: Text -> Handler RepHtml
      +getEchoR theText = do
      +    defaultLayout $ do
      +        $(widgetFile "echo")
      +

      At this point, our web application is structured between different files. Handler are grouped, we use Data.Text and our views are in templates. It is the time to make a slightly more complex example.

      +

      Mirror

      +

      Neo touching a mirror

      +

      Let’s make another minimal application. You should see a form containing a text field and a validation button. When you enter some text (for example “Jormungad”) and validate, the next page present you the content and its reverse appended to it. In our example it should return “JormungaddagnumroJ”.

      +

      First, add a new route:

      +
      +/mirror MirrorR GET POST
      +
      + +

      This time the path /mirror will accept GET and POST requests. Add the corresponding new Handler file:

      +
      module Handler.Mirror where
      +
      +import Import
      +import qualified Data.Text as T
      +
      +getMirrorR :: Handler RepHtml
      +getMirrorR = do
      +    defaultLayout $ do
      +        $(widgetFile "mirror")
      +
      +postMirrorR :: Handler RepHtml
      +postMirrorR =  do
      +        postedText <- runInputPost $ ireq textField "content"
      +        defaultLayout $ do
      +            $(widgetFile "posted")
      +

      Don’t forget to declare it inside yosog.cabal and Application.hs.

      +

      We will need to use the reverse function provided by Data.Text which explain the additional import.

      +

      The only new thing here is the line that get the POST parameter named “content”. If you want to know more detail about it and form in general you can take look at the yesod book.

      +

      Create the two corresponding templates:

      +
      <h1> Enter your text
      +<form method=post action=@{MirrorR}>
      +    <input type=text name=content>
      +    <input type=submit>
      +
      <h1>You've just posted
      +<p>#{postedText}#{T.reverse postedText}
      +<hr>
      +<p><a href=@{MirrorR}>Get back
      +

      And that is all. This time, we won’t need to clean up. We may have used another way to generate the form but we’ll see this in the next section.

      +

      Just try it by clicking here.

      +

      Also you can try to enter strange values. Like before, your application is quite secure.

      +

      A Blog

      +

      We saw how to retrieve %http parameters. It is the time to save things into a database.

      +

      As before add some routes inside config/routes:

      +
      +/blog               BlogR       GET POST
      +/blog/#ArticleId    ArticleR    GET
      +
      + +

      This example will be very minimal:

      +
        +
      • GET on /blog should display the list of articles.
      • +
      • POST on /blog should create a new article
      • +
      • GET on /blog/<article id> should display the content of the article.
      • +
      +

      First we declare another model object. Append the following content to config/models:

      +
      +Article
      +    title   Text
      +    content Html 
      +    deriving
      +
      + +

      As Html is not an instance of Read, Show and Eq, we had to add the deriving line. If you forget it, there will be an error.

      +

      After the route and the model, we write the handler. First, declare a new Handler module. Add import Handler.Blog inside Application.hs and add it into yosog.cabal. Let’s write the content of Handler/Blog.hs. We start by declaring the module and by importing some block necessary to handle Html in forms.

      +
      module Handler.Blog
      +    ( getBlogR
      +    , postBlogR
      +    , getArticleR
      +    )
      +where
      +
      +import Import
      +import Data.Monoid
      +
      +-- to use Html into forms
      +import Yesod.Form.Nic (YesodNic, nicHtmlField)
      +instance YesodNic App
      +

      Remark: it is a best practice to add the YesodNic instance inside Foundation.hs. I put this definition here to make things easier but you should see a warning about this orphan instance. To put the include inside Foundation.hs is left as an exercice to the reader.

      +

      Hint: Do not forget to put YesodNic and nicHtmlField inside the exported objects of the module.

      +
      entryForm :: Form Article
      +entryForm = renderDivs $ Article
      +    <$> areq   textField "Title" Nothing
      +    <*> areq   nicHtmlField "Content" Nothing
      +

      This function defines a form for adding a new article. Don’t pay attention to all the syntax. If you are curious you can take a look at Applicative Functor. You just have to remember areq is for required form input. Its arguments being: areq type label default_value.

      +
      -- The view showing the list of articles
      +getBlogR :: Handler RepHtml
      +getBlogR = do
      +    -- Get the list of articles inside the database.
      +    articles <- runDB $ selectList [] [Desc ArticleTitle]
      +    -- We'll need the two "objects": articleWidget and enctype
      +    -- to construct the form (see templates/articles.hamlet).
      +    (articleWidget, enctype) <- generateFormPost entryForm
      +    defaultLayout $ do
      +        $(widgetFile "articles")
      +

      This handler should display a list of articles. We get the list from the DB and we construct the form. Just take a look at the corresponding template:

      +
      <h1> Articles
      +$if null articles
      +    -- Show a standard message if there is no article
      +    <p> There are no articles in the blog
      +$else
      +    -- Show the list of articles
      +    <ul>
      +        $forall Entity articleId article <- articles
      +            <li> 
      +                <a href=@{ArticleR articleId} > #{articleTitle article}
      +<hr>
      +  <form method=post enctype=#{enctype}>
      +    ^{articleWidget}
      +    <div>
      +        <input type=submit value="Post New Article">
      +

      You should remark we added some logic inside the template. There is a test and a “loop”.

      +

      Another very interesting part is the creation of the form. The articleWidget was created by yesod. We have given him the right parameters (input required or optional, labels, default values). And now we have a protected form made for us. But we have to create the submit button.

      +

      Get back to Handler/Blog.hs.

      +
      -- we continue Handler/Blog.hs
      +postBlogR :: Handler RepHtml
      +postBlogR = do
      +    ((res,articleWidget),enctype) <- runFormPost entryForm
      +    case res of 
      +         FormSuccess article -> do 
      +            articleId <- runDB $ insert article
      +            setMessage $ toHtml $ (articleTitle article) <> " created"
      +            redirect $ ArticleR articleId 
      +         _ -> defaultLayout $ do
      +                setTitle "Please correct your entry form"
      +                $(widgetFile "articleAddError")
      +

      This function should be used to create a new article. We handle the form response. If there is an error we display an error page. For example if we left some required value blank. If things goes right:

      +
        +
      • we add the new article inside the DB (runDB $ insert article)
      • +
      • we add a message to be displayed (setMessage $ ...)
      • +
      • we are redirected to the article web page.
      • +
      +

      Here is the content of the error Page:

      +
      <form method=post enctype=#{enctype}>
      +    ^{articleWidget}
      +    <div>
      +        <input type=submit value="Post New Article">
      +

      Finally we need to display an article:

      +
      getArticleR :: ArticleId -> Handler RepHtml
      +getArticleR articleId = do
      +    article <- runDB $ get404 articleId
      +    defaultLayout $ do
      +        setTitle $ toHtml $ articleTitle article
      +        $(widgetFile "article")
      +

      The get404 function try to do a get on the DB. If it fails it return a 404 page. The rest should be clear. Here is the content of templates/article.hamlet:

      +
      <h1> #{articleTitle article}
      +<article> #{articleContent article}
      +

      The blog system is finished. Just for fun, you can try to create an article with the following content:

      +
      <p>A last try to <em>cross script</em> 
      +   and <em>SQL injection</em></p>
      +<p>Here is the first try: 
      +   <script>alert("You loose");</script></p>
      +<p> And Here is the last </p>
      +"); DROP TABLE ARTICLE;;
      +

      Conclusion

      +

      This is the end of this tutorial. I made it very minimal.

      +

      If you already know Haskell and you want to go further, you should take a look at the recent i18n blog tutorial. It will be obvious I inspired my own tutorial on it. You’ll learn in a very straightforward way how easy it is to use authorizations, Time and internationalization.

      +

      If, on the other hand you don’t know Haskell. Then you shouldn’t jump directly to web programming. Haskell is a very complex and unusual language. My advice to go as fast as possible in using Haskell for web programming is:

      +
        +
      1. Start by try Haskell in your browser
      2. +
      3. Then read the excellent Learn you a Haskell for Great Good
      4. +
      5. If you have difficulties in understanding concepts like monads, you should really read these articles. For me they were enlightening.
      6. +
      7. If you feel confident, you should be able to follows the yesod book and if you find difficult to follows the yesod book, you should read real world Haskell first (it is a must read).
      8. +
      +

      Also, note that:

      +
        +
      • haskell.org is full of excellent resources.
      • +
      • hoogle will be very useful
      • +
      • Use hlint as soon as possible to get good habits.
      • +
      +

      As you should see, if you don’t already know Haskell, the path is long but I guaranty you it will be very rewarding!

      +

      ps: You can download the source of this yesod blog tutorial at github.com/yogsototh/yosog.

      +
      +
      +
        +
      1. By view I mean yesod widget’s hamlet, lucius and julius files.

      2. +
      +
      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2012-01-15 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/en/blog/feed/feed.xml b/Scratch/en/blog/feed/feed.xml new file mode 100644 index 0000000..a28384f --- /dev/null +++ b/Scratch/en/blog/feed/feed.xml @@ -0,0 +1,5684 @@ + + + yannesposito - Yann Esposito + + + http://yannesposito.com/Scratch/en/blog/feed/feed.xml + + Yann Esposito + yann.esposito@gmail.com + + 2012-12-12T00:00:00Z + + Category Theory Presentation + + http://yannesposito.com/Scratch/en/blog/Category-Theory-Presentation/index.html + 2012-12-12T00:00:00Z + 2012-12-12T00:00:00Z + + +

      Yesterday I was happy to make a presentation about Category Theory at Riviera Scala Clojure Meetup (note I used only Haskell for my examples).

      + + + +

      If you don't want to read them through an HTML presentations framework or downloading a big PDF +just continue to read as a standard web page. +

      + +
      +\(\newcommand{\F}{\mathbf{F}}\) +\(\newcommand{\E}{\mathbf{E}}\) +\(\newcommand{\C}{\mathcal{C}}\) +\(\newcommand{\D}{\mathcal{D}}\) +\(\newcommand{\id}{\mathrm{id}}\) +\(\newcommand{\ob}[1]{\mathrm{ob}(#1)}\) +\(\newcommand{\hom}[1]{\mathrm{hom}(#1)}\) +\(\newcommand{\Set}{\mathbf{Set}}\) +\(\newcommand{\Mon}{\mathbf{Mon}}\) +\(\newcommand{\Vec}{\mathbf{Vec}}\) +\(\newcommand{\Grp}{\mathbf{Grp}}\) +\(\newcommand{\Rng}{\mathbf{Rng}}\) +\(\newcommand{\ML}{\mathbf{ML}}\) +\(\newcommand{\Hask}{\mathbf{Hask}}\) +\(\newcommand{\Cat}{\mathbf{Cat}}\) +\(\newcommand{\fmap}{\mathtt{fmap}}\) +
      + +
      +

      Category Theory & Programming

      +
      for Rivieria Scala Clojure (Note this presentation uses Haskell)
      +by Yann Esposito +
      + + @yogsototh, + + + +yogsototh + +
      +
      +
      +

      Plan

      +
        +
      • General overview
      • +
      • Definitions
      • +
      • Applications
      • +
      +
      +
      +

      Not really about: Cat & glory

      +
      +Cat n glory
      credit to Tokuhiro Kawai (川井徳寛)
      +
      + +
      +
      +

      General Overview

      +
      +Samuel Eilenberg Saunders Mac Lane +
      + +

      Recent Math Field
      1942-45, Samuel Eilenberg & Saunders Mac Lane

      +

      Certainly one of the more abstract branches of math

      +
        +
      • New math foundation
        formalism abstraction, package entire theory
      • +
      • Bridge between disciplines
        Physics, Quantum Physics, Topology, Logic, Computer Science
      • +
      +

      +★: When is one thing equal to some other thing?, Barry Mazur, 2007
      ☆: Physics, Topology, Logic and Computation: A Rosetta Stone, John C. Baez, Mike Stay, 2009 +

      + +
      +
      +

      From a Programmer perspective

      +
      +

      Category Theory is a new language/framework for Math

      +
      +
        +
      • Another way of thinking
      • +
      • Extremely efficient for generalization
      • +
      +
      +
      +

      Math Programming relation

      +Buddha Fractal +

      Programming is doing Math

      +

      Strong relations between type theory and category theory.

      +

      Not convinced?
      Certainly a vocabulary problem.

      +

      One of the goal of Category Theory is to create a homogeneous vocabulary between different disciplines.

      +
      +
      +

      Vocabulary

      +mind blown +

      Math vocabulary used in this presentation:

      +
      +

      Category, Morphism, Associativity, Preorder, Functor, Endofunctor, Categorial property, Commutative diagram, Isomorph, Initial, Dual, Monoid, Natural transformation, Monad, Klesli arrows, κατα-morphism, ...

      +
      +
      +
      +

      Programmer Translation

      +lolcat + + + + + + + + +
      +Mathematician + +Programmer +
      +Morphism + +Arrow +
      +Monoid + +String-like +
      +Preorder + +Acyclic graph +
      +Isomorph + +The same +
      +Natural transformation + +rearrangement function +
      +Funny Category + +LOLCat +
      + +
      +
      +

      Plan

      +
        +
      • General overview
      • +
      • Definitions +
          +
        • Category
        • +
        • Intuition
        • +
        • Examples
        • +
        • Functor
        • +
        • Examples
        • +
        +
      • +
      • Applications
      • +
      +
      +
      +

      Category

      + +

      A way of representing things and ways to go between things.

      + +

      A Category \(\mathcal{C}\) is defined by:

      +
        +
      • Objects \(\ob{C}\),
      • +
      • Morphisms \(\hom{C}\),
      • +
      • a Composition law (∘)
      • +
      • obeying some Properties.
      • +
      +
      +
      +

      Category: Objects

      + +objects + +

      \(\ob{\mathcal{C}}\) is a collection

      +
      +
      +

      Category: Morphisms

      + +morphisms + +

      \(A\) and \(B\) objects of \(\C\)
      +\(\hom{A,B}\) is a collection of morphisms
      +\(f:A→B\) denote the fact \(f\) belongs to \(\hom{A,B}\)

      +

      \(\hom{\C}\) the collection of all morphisms of \(\C\)

      +
      +
      +

      Category: Composition

      +

      Composition (∘): associate to each couple \(f:A→B, g:B→C\) + $$g∘f:A\rightarrow C$$ +

      +composition +
      +
      +

      Category laws: neutral element

      +

      for each object \(X\), there is an \(\id_X:X→X\),
      +such that for each \(f:A→B\):

      +identity +
      +
      +

      Category laws: Associativity

      +

      Composition is associative:

      +associative composition +
      +
      +

      Commutative diagrams

      + +

      Two path with the same source and destination are equal.

      +
      + Commutative Diagram (Associativity) +
      + \((h∘g)∘f = h∘(g∘f) \) +
      +
      +
      + Commutative Diagram (Identity law) +
      + \(id_B∘f = f = f∘id_A \) +
      +
      +
      +
      +

      Question Time!

      + +
      + +
      +- French-only joke - +
      +
      +
      +
      +

      Can this be a category?

      +

      \(\ob{\C},\hom{\C}\) fixed, is there a valid ∘?

      +
      + Category example 1 +
      + YES +
      +
      +
      + Category example 2 +
      + no candidate for \(g∘f\) +
      NO +
      +
      +
      + Category example 3 +
      + YES +
      +
      +
      +
      +

      Can this be a category?

      +
      + Category example 4 +
      + no candidate for \(f:C→B\) +
      NO +
      +
      +
      + Category example 5 +
      + \((h∘g)∘f=\id_B∘f=f\)
      + \(h∘(g∘f)=h∘\id_A=h\)
      + but \(h≠f\)
      + NO +
      +
      +
      +
      +

      Categories Examples

      + +
      +Basket of cats +
      +- Basket of Cats - +
      +
      +
      +
      +

      Category \(\Set\)

      + +
        +
      • \(\ob{\Set}\) are all the sets
      • +
      • \(\hom{E,F}\) are all functions from \(E\) to \(F\)
      • +
      • ∘ is functions composition
      • +
      + +
        +
      • \(\ob{\Set}\) is a proper class ; not a set
      • +
      • \(\hom{E,F}\) is a set
      • +
      • \(\Set\) is then a locally small category
      • +
      +
      +
      +

      Categories Everywhere?

      +Cats everywhere +
        +
      • \(\Mon\): (monoids, monoid morphisms,∘)
      • +
      • \(\Vec\): (Vectorial spaces, linear functions,∘)
      • +
      • \(\Grp\): (groups, group morphisms,∘)
      • +
      • \(\Rng\): (rings, ring morphisms,∘)
      • +
      • Any deductive system T: (theorems, proofs, proof concatenation)
      • +
      • \( \Hask\): (Haskell types, functions, (.) )
      • +
      • ...
      • +
      +
      +
      +

      Smaller Examples

      + +

      Strings

      +Monoids are one object categories +
        +
      • \(\ob{Str}\) is a singleton
      • +
      • \(\hom{Str}\) each string
      • +
      • ∘ is concatenation (++)
      • +
      +
        +
      • "" ++ u = u = u ++ ""
      • +
      • (u ++ v) ++ w = u ++ (v ++ w)
      • +
      +
      +
      +

      Finite Example?

      + +

      Graph

      +
      +Each graph is a category +
      +
        +
      • \(\ob{G}\) are vertices
      • +
      • \(\hom{G}\) each path
      • +
      • ∘ is path concatenation
      • +
      +
      • \(\ob{G}=\{X,Y,Z\}\), +
      • \(\hom{G}=\{ε,α,β,γ,αβ,βγ,...\}\) +
      • \(αβ∘γ=αβγ\) +
      +
      +
      +

      Number construction

      + +

      Each Numbers as a whole category

      +Each number as a category +
      +
      +

      Degenerated Categories: Monoids

      + +Monoids are one object categories +

      Each Monoid \((M,e,⊙): \ob{M}=\{∙\},\hom{M}=M,\circ = ⊙\)

      +

      Only one object.

      +

      Examples:

      +
      • (Integer,0,+), (Integer,1,*), +
      • (Strings,"",++), for each a, ([a],[],++) +
      +
      +
      +

      Degenerated Categories: Preorders \((P,≤)\)

      + +
      • \(\ob{P}={P}\), +
      • \(\hom{x,y}=\{x≤y\} ⇔ x≤y\), +
      • \((y≤z) \circ (x≤y) = (x≤z) \) +
      + +

      At most one morphism between two objects.

      + +preorder category +
      +
      +

      Degenerated Categories: Discrete Categories

      + +Any set can be a category +

      Any Set

      +

      Any set \(E: \ob{E}=E, \hom{x,y}=\{x\} ⇔ x=y \)

      +

      Only identities

      +
      +
      +

      Choice

      +

      The same object can be seen in many different way as a category.

      +

      You can choose what are object, morphisms and composition.

      +

      ex: Str and discrete(Σ*)

      +
      +
      +

      Categorical Properties

      + +

      Any property which can be expressed in term of category, objects, morphism and composition.

      + +
      • Dual: \(\D\) is \(\C\) with reversed morphisms. +
      • Initial: \(Z\in\ob{\C}\) s.t. \(∀Y∈\ob{\C}, \#\hom{Z,Y}=1\) +
        Unique ("up to isormophism") +
      • Terminal: \(T\in\ob{\C}\) s.t. \(T\) is initial in the dual of \(\C\) +
      • Functor: structure preserving mapping between categories +
      • ... +
      +
      +
      +

      Isomorph

      +

      isomorph cats isomorphism: \(f:A→B\) which can be "undone" i.e.
      \(∃g:B→A\), \(g∘f=id_A\) & \(f∘g=id_B\)
      in this case, \(A\) & \(B\) are isomorphic.

      +

      A≌B means A and B are essentially the same.
      In Category Theory, = is in fact mostly .
      For example in commutative diagrams.

      +
      +
      +

      Functor

      + +

      A functor is a mapping between two categories. +Let \(\C\) and \(\D\) be two categories. +A functor \(\F\) from \(\C\) to \(\D\):

      +
        +
      • Associate objects: \(A\in\ob{\C}\) to \(\F(A)\in\ob{\D}\)
      • +
      • Associate morphisms: \(f:A\to B\) to \(\F(f) : \F(A) \to \F(B)\) + such that +
          +
        • \( \F (\)\(\id_X\)\()= \)\(\id\)\(\vphantom{\id}_{\F(}\)\(\vphantom{\id}_X\)\(\vphantom{\id}_{)} \),
        • +
        • \( \F (\)\(g∘f\)\()= \)\( \F(\)\(g\)\() \)\(\circ\)\( \F(\)\(f\)\() \)
        • +
        +
      • +
      +
      +
      +

      Functor Example (ob → ob)

      + +Functor +
      +
      +

      Functor Example (hom → hom)

      + +Functor +
      +
      +

      Functor Example

      + +Functor +
      +
      +

      Endofunctors

      + +

      An endofunctor for \(\C\) is a functor \(F:\C→\C\).

      +Endofunctor +
      +
      +

      Category of Categories

      + + + +

      Categories and functors form a category: \(\Cat\)

      +
      • \(\ob{\Cat}\) are categories +
      • \(\hom{\Cat}\) are functors +
      • ∘ is functor composition +
      +
      +
      +

      Plan

      +
        +
      • General overview
      • +
      • Definitions
      • +
      • Applications +
          +
        • \(\Hask\) category +
        • Functors +
        • Natural transformations +
        • Monads +
        • κατα-morphisms +
        +
      • +
      +
      +
      +

      Hask

      + +

      Category \(\Hask\):

      + +Haskell Category Representation + +
      • +\(\ob{\Hask} = \) Haskell types +
      • +\(\hom{\Hask} = \) Haskell functions +
      • +∘ = (.) Haskell function composition +
      + +

      Forget glitches because of undefined.

      +
      +
      +

      Haskell Kinds

      +

      In Haskell some types can take type variable(s). Typically: [a].

      +

      Types have kinds; The kind is to type what type is to function. Kind are the types for types (so meta).

      +
      Int, Char :: *
      +[], Maybe :: * -> *
      +(,), (->) :: * -> * -> *
      +[Int], Maybe Char, Maybe [Int] :: *
      +
      +
      +

      Haskell Types

      +

      Sometimes, the type determine a lot about the function:

      +
      fst :: (a,b) -> a -- Only one choice
      +snd :: (a,b) -> b -- Only one choice
      +f :: a -> [a]     -- Many choices
      +-- Possibilities: f x=[], or [x], or [x,x] or [x,...,x]
      +
      +? :: [a] -> [a] -- Many choices
      +-- can only rearrange: duplicate/remove/reorder elements
      +-- for example: the type of addOne isn't [a] -> [a]
      +addOne l = map (+1) l
      +-- The (+1) force 'a' to be a Num.
      + +

      +

      ★:Theorems for free!, Philip Wadler, 1989

      +
      +
      +

      Haskell Functor vs \(\Hask\) Functor

      + +

      A Haskell Functor is a type F :: * -> * which belong to the type class Functor ; thus instantiate +fmap :: (a -> b) -> (F a -> F b). + +

      & F: \(\ob{\Hask}→\ob{\Hask}\)
      & fmap: \(\hom{\Hask}→\hom{\Hask}\) + +

      The couple (F,fmap) is a \(\Hask\)'s functor if for any x :: F a:

      +
      • fmap id x = x +
      • fmap (f.g) x= (fmap f . fmap g) x +
      +
      +
      +

      Haskell Functors Example: Maybe

      + +
      data Maybe a = Just a | Nothing
      +instance Functor Maybe where
      +    fmap :: (a -> b) -> (Maybe a -> Maybe b)
      +    fmap f (Just a) = Just (f a)
      +    fmap f Nothing = Nothing
      +
      fmap (+1) (Just 1) == Just 2
      +fmap (+1) Nothing  == Nothing
      +fmap head (Just [1,2,3]) == Just 1
      +
      +
      +

      Haskell Functors Example: List

      + +
      instance Functor ([]) where
      +	fmap :: (a -> b) -> [a] -> [b]
      +	fmap = map
      +
      fmap (+1) [1,2,3]           == [2,3,4]
      +fmap (+1) []                == []
      +fmap head [[1,2,3],[4,5,6]] == [1,4]
      +
      +
      +

      Haskell Functors for the programmer

      +

      Functor is a type class used for types that can be mapped over.

      +
        +
      • Containers: [], Trees, Map, HashMap...
      • +
      • "Feature Type": +
          +
        • Maybe a: help to handle absence of a.
          Ex: safeDiv x 0 ⇒ Nothing
        • +
        • Either String a: help to handle errors
          Ex: reportDiv x 0 ⇒ Left "Division by 0!"
        • +
      • +
      +
      +
      +

      Haskell Functor intuition

      + +

      Put normal function inside a container. Ex: list, trees...

      + +Haskell Functor as a box play +

      +
      +

      Haskell Functor properties

      + +

      Haskell Functors are:

      + +
      • endofunctors ; \(F:\C→\C\) here \(\C = \Hask\), +
      • a couple (Object,Morphism) in \(\Hask\). +
      +
      +
      +

      Functor as boxes

      + +

      Haskell functor can be seen as boxes containing all Haskell types and functions. +Haskell types look like a fractal:

      + +Haskell functor representation +
      +
      +

      Functor as boxes

      + +

      Haskell functor can be seen as boxes containing all Haskell types and functions. +Haskell types look like a fractal:

      + +Haskell functor representation +
      +
      +

      Functor as boxes

      + +

      Haskell functor can be seen as boxes containing all Haskell types and functions. +Haskell types look like a fractal:

      + +Haskell functor representation +
      +
      +

      "Non Haskell" Hask's Functors

      +

      A simple basic example is the \(id_\Hask\) functor. It simply cannot be expressed as a couple (F,fmap) where

      +
        +
      • F::* -> *
      • +
      • fmap :: (a -> b) -> (F a) -> (F b)
      • +
      +

      Another example:

      +
        +
      • F(T)=Int
      • +
      • F(f)=\_->0
      • +
      +
      +
      +

      Also Functor inside \(\Hask\)

      +

      \(\mathtt{[a]}∈\ob{\Hask}\) but is also a category. Idem for Int.

      +

      length is a Functor from the category [a] to the category Int:

      +
        +
      • \(\ob{\mathtt{[a]}}=\{∙\}\)
      • +
      • \(\hom{\mathtt{[a]}}=\mathtt{[a]}\)
      • +
      • \(∘=\mathtt{(++)}\)
      • +
      +

      +
        +
      • \(\ob{\mathtt{Int}}=\{∙\}\)
      • +
      • \(\hom{\mathtt{Int}}=\mathtt{Int}\)
      • +
      • \(∘=\mathtt{(+)}\)
      • +
      +
      +
      • id: length [] = 0 +
      • comp: length (l ++ l') = (length l) + (length l') +
      +
      +
      +

      Category of \(\Hask\) Endofunctors

      +Category of Hask endofunctors +
      +
      +

      Category of Functors

      +

      If \(\C\) is small (\(\hom{\C}\) is a set). All functors from \(\C\) to some category \(\D\) form the category \(\mathrm{Func}(\C,\D)\).

      +
        +
      • \(\ob{\mathrm{Func}(\C,\D)}\): Functors \(F:\C→\D\)
      • +
      • \(\hom{\mathrm{Func}(\C,\D)}\): natural transformations
      • +
      • ∘: Functor composition
      • +
      +

      \(\mathrm{Func}(\C,\C)\) is the category of endofunctors of \(\C\).

      +
      +
      +

      Natural Transformations

      +

      Let \(F\) and \(G\) be two functors from \(\C\) to \(\D\).

      +

      Natural transformation commutative diagram A natural transformation: familly η ; \(η_X\in\hom{\D}\) for \(X\in\ob{\C}\) s.t.

      +

      ex: between Haskell functors; F a -> G a
      Rearragement functions only.

      +
      +
      +

      Natural Transformation Examples (1/4)

      +
      data List a = Nil | Cons a (List a)
      +toList :: [a] -> List a
      +toList [] = Nil
      +toList (x:xs) = Cons x (toList xs)
      +

      toList is a natural transformation. It is also a morphism from [] to List in the Category of \(\Hask\) endofunctors.

      +natural transformation commutative diagram +
      +natural transformation commutative diagram +
      + +
      +
      +

      Natural Transformation Examples (2/4)

      +
      data List a = Nil | Cons a (List a)
      +toHList :: List a -> [a]
      +toHList Nil = []
      +toHList (Cons x xs) = x:toHList xs
      +

      toHList is a natural transformation. It is also a morphism from List to [] in the Category of \(\Hask\) endofunctors.

      +natural transformation commutative diagram +
      +natural transformation commutative diagram
      toList . toHList = id & toHList . toList = id &
      therefore [] & List are isomorph.
      +
      + +
      +
      +

      Natural Transformation Examples (3/4)

      +
      toMaybe :: [a] -> Maybe a
      +toMaybe [] = Nothing
      +toMaybe (x:xs) = Just x
      +

      toMaybe is a natural transformation. It is also a morphism from [] to Maybe in the Category of \(\Hask\) endofunctors.

      +natural transformation commutative diagram +
      +natural transformation commutative diagram +
      + +
      +
      +

      Natural Transformation Examples (4/4)

      +
      mToList :: Maybe a -> [a]
      +mToList Nothing = []
      +mToList Just x  = [x]
      +

      toMaybe is a natural transformation. It is also a morphism from [] to Maybe in the Category of \(\Hask\) endofunctors.

      +natural transformation commutative diagram +
      +relation between [] and Maybe
      There is no isomorphism.
      Hint: Bool lists longer than 1.
      +
      + +
      +
      +

      Composition problem

      +

      The Problem; example with lists:

      +
      f x = [x]       ⇒ f 1 = [1]   ⇒ (f.f) 1 = [[1]] ✗
      +g x = [x+1]     ⇒ g 1 = [2]   ⇒ (g.g) 1 = ERROR [2]+1 ✗
      +h x = [x+1,x*3] ⇒ h 1 = [2,3] ⇒ (h.h) 1 = ERROR [2,3]+1 ✗ 
      + +

      The same problem with most f :: a -> F a functions and functor F.

      +
      +
      +

      Composition Fixable?

      +

      How to fix that? We want to construct an operator which is able to compose:

      +

      f :: a -> F b & g :: b -> F c.

      +

      More specifically we want to create an operator ◎ of type

      +

      ◎ :: (b -> F c) -> (a -> F b) -> (a -> F c)

      +

      Note: if F = I, ◎ = (.).

      +
      +
      +

      Fix Composition (1/2)

      +

      Goal, find: ◎ :: (b -> F c) -> (a -> F b) -> (a -> F c)
      f :: a -> F b, g :: b -> F c:

      +
        +
      • (g ◎ f) x ???
      • +
      • First apply f to xf x :: F b
      • +
      • Then how to apply g properly to an element of type F b?
      • +
      +
      +
      +

      Fix Composition (2/2)

      +

      Goal, find: ◎ :: (b -> F c) -> (a -> F b) -> (a -> F c)
      f :: a -> F b, g :: b -> F c, f x :: F b:

      +
        +
      • Use fmap :: (t -> u) -> (F t -> F u)!
      • +
      • (fmap g) :: F b -> F (F c) ; (t=b, u=F c)
      • +
      • (fmap g) (f x) :: F (F c) it almost WORKS!
      • +
      • We lack an important component, join :: F (F c) -> F c
      • +
      • (g ◎ f) x = join ((fmap g) (f x))
        ◎ is the Kleisli composition; in Haskell: <=< (in Control.Monad).
      • +
      +
      +
      +

      Necessary laws

      +

      For ◎ to work like composition, we need join to hold the following properties:

      +
        +
      • join (join (F (F (F a))))=join (F (join (F (F a))))
      • +
      • abusing notations denoting join by ⊙; this is equivalent to
        (F ⊙ F) ⊙ F = F ⊙ (F ⊙ F)
      • +
      • There exists η :: a -> F a s.t.
        η⊙F=F=F⊙η
      • +
      +
      +
      +

      Klesli composition

      +

      Now the composition works as expected. In Haskell ◎ is <=< in Control.Monad.

      +

      g <=< f = \x -> join ((fmap g) (f x))

      +
      f x = [x]       ⇒ f 1 = [1]   ⇒ (f <=< f) 1 = [1] ✓
      +g x = [x+1]     ⇒ g 1 = [2]   ⇒ (g <=< g) 1 = [3] ✓
      +h x = [x+1,x*3] ⇒ h 1 = [2,3] ⇒ (h <=< h) 1 = [3,6,4,9] ✓
      + +
      +
      +

      We reinvented Monads!

      +

      A monad is a triplet (M,⊙,η) where

      +
        +
      • \(M\) an Endofunctor (to type a associate M a)
      • +
      • \(⊙:M×M→M\) a nat. trans. (i.e. ⊙::M (M a) → M a ; join)
      • +
      • \(η:I→M\) a nat. trans. (\(I\) identity functor ; η::a → M a)
      • +
      +

      Satisfying

      +
        +
      • \(M ⊙ (M ⊙ M) = (M ⊙ M) ⊙ M\)
      • +
      • \(η ⊙ M = M = M ⊙ η\)
      • +
      +
      +
      +

      Compare with Monoid

      +

      A Monoid is a triplet \((E,∙,e)\) s.t.

      +
        +
      • \(E\) a set
      • +
      • \(∙:E×E→E\)
      • +
      • \(e:1→E\)
      • +
      +

      Satisfying

      +
        +
      • \(x∙(y∙z) = (x∙y)∙z, ∀x,y,z∈E\)
      • +
      • \(e∙x = x = x∙e, ∀x∈E\)
      • +
      +
      +
      +

      Monads are just Monoids

      +
      +

      A Monad is just a monoid in the category of endofunctors, what's the problem?

      +
      +

      The real sentence was:

      +
      +

      All told, a monad in X is just a monoid in the category of endofunctors of X, with product × replaced by composition of endofunctors and unit set by the identity endofunctor.

      +
      +
      +
      +

      Example: List

      +
        +
      • [] :: * -> * an Endofunctor
      • +
      • \(⊙:M×M→M\) a nat. trans. (join :: M (M a) -> M a)
      • +
      • \(η:I→M\) a nat. trans.
      • +
      +
      -- In Haskell ⊙ is "join" in "Control.Monad"
      +join :: [[a]] -> [a]
      +join = concat
      +
      +-- In Haskell the "return" function (unfortunate name)
      +η :: a -> [a]
      +η x = [x]
      + +
      +
      +

      Example: List (law verification)

      +

      Example: List is a functor (join is ⊙)

      +
        +
      • \(M ⊙ (M ⊙ M) = (M ⊙ M) ⊙ M\)
      • +
      • \(η ⊙ M = M = M ⊙ η\)
      • +
      +
      join [ join [[x,y,...,z]] ] = join [[x,y,...,z]]
      +                            = join (join [[[x,y,...,z]]])
      +join (η [x]) = [x] = join [η x]
      + +

      Therefore ([],join,η) is a monad.

      +
      +
      +

      Monads useful?

      +

      A LOT of monad tutorial on the net. Just one example; the State Monad

      +

      DrawScene to State Screen DrawScene ; still pure.

      +
      main = drawImage (width,height)
      +
      +drawImage :: Screen -> DrawScene
      +drawImage screen = do
      +    drawPoint p screen
      +    drawCircle c screen
      +    drawRectangle r screen
      +
      +drawPoint point screen = ...
      +drawCircle circle screen = ...
      +drawRectangle rectangle screen = ...
      +
      main = do
      +    put (Screen 1024 768)
      +    drawImage
      +
      +drawImage :: State Screen DrawScene
      +drawImage = do
      +    drawPoint p
      +    drawCircle c
      +    drawRectangle r
      +
      +drawPoint :: Point ->
      +               State Screen DrawScene
      +drawPoint p = do
      +    Screen width height <- get
      +    ...
      +
      +
      +

      fold

      +fold +
      +
      +

      κατα-morphism

      +catamorphism +
      +
      +

      κατα-morphism: fold generalization

      +

      acc type of the "accumulator":
      fold :: (acc -> a -> acc) -> acc -> [a] -> acc

      +

      Idea: put the accumulated value inside the type.

      +
      -- Equivalent to fold (+1) 0 "cata"
      +(Cons 'c' (Cons 'a' (Cons 't' (Cons 'a' Nil))))
      +(Cons 'c' (Cons 'a' (Cons 't' (Cons 'a' 0))))
      +(Cons 'c' (Cons 'a' (Cons 't' 1)))
      +(Cons 'c' (Cons 'a' 2))
      +(Cons 'c' 3)
      +4
      + +

      But where are all the informations? (+1) and 0?

      +
      +
      +

      κατα-morphism: Missing Information

      +

      Where is the missing information?

      +
        +
      • Functor operator fmap
      • +
      • Algebra representing the (+1) and also knowing about the 0.
      • +
      +

      First example, make length on [Char]

      +
      +
      +

      κατα-morphism: Type work

      +
      
      +data StrF a = Cons Char a | Nil
      +data Str' = StrF Str'
      +
      +-- generalize the construction of Str to other datatype
      +-- Mu: type fixed point
      +-- Mu :: (* -> *) -> *
      +
      +data Mu f = InF { outF :: f (Mu f) }
      +data Str = Mu StrF
      +
      +-- Example
      +foo=InF { outF = Cons 'f'
      +        (InF { outF = Cons 'o'
      +            (InF { outF = Cons 'o'
      +                (InF { outF = Nil })})})}
      + +
      +
      +

      κατα-morphism: missing information retrieved

      +
      type Algebra f a = f a -> a
      +instance Functor (StrF a) =
      +    fmap f (Cons c x) = Cons c (f x)
      +    fmap _ Nil = Nil
      + +
      cata :: Functor f => Algebra f a -> Mu f -> a
      +cata f = f . fmap (cata f) . outF
      + +
      +
      +

      κατα-morphism: Finally length

      +

      All needed information for making length.

      +
      instance Functor (StrF a) =
      +    fmap f (Cons c x) = Cons c (f x)
      +    fmap _ Nil = Nil
      +
      +length' :: Str -> Int
      +length' = cata phi where
      +    phi :: Algebra StrF Int -- StrF Int -> Int
      +    phi (Cons a b) = 1 + b
      +    phi Nil = 0
      +
      +main = do
      +    l <- length' $ stringToStr "Toto"
      +    ...
      +
      +
      +

      κατα-morphism: extension to Trees

      +

      Once you get the trick, it is easy to extent to most Functor.

      +
      type Tree = Mu TreeF
      +data TreeF x = Node Int [x]
      +
      +instance Functor TreeF where
      +  fmap f (Node e xs) = Node e (fmap f xs)
      +
      +depth = cata phi where
      +  phi :: Algebra TreeF Int -- TreeF Int -> Int
      +  phi (Node x sons) = 1 + foldr max 0 sons
      +
      +
      +

      Conclusion

      +

      Category Theory oriented Programming:

      +
        +
      • Focus on the type and operators
      • +
      • Extreme generalisation
      • +
      • Better modularity
      • +
      • Better control through properties of types
      • +
      +

      No cat were harmed in the making of this presentation.

      +
      +]]>
      +
      + + Haskell Progressive Example + + http://yannesposito.com/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/index.html + 2012-06-15T00:00:00Z + 2012-06-15T00:00:00Z + The B in Benoît B. Mandelbrot stand for Benoît B. Mandelbrot

      +
      + +

      tl;dr: A progressive Haskell example. A Mandelbrot set extended in 3D, rendered using OpenGL and coded with Haskell. In the end the code will be very clean. The significant stuff will be in a pure functional bubble. The display details will be put in an external module playing the role of a wrapper. Imperative language could also benefit from this functional organization.

      +
      + +

      Introduction

      +

      In my preceding article I introduced Haskell.

      +

      This article goes further. It will show how to use functional programming with interactive programs. But more than that, it will show how to organize your code in a functional way. This article is more about functional paradigm than functional language. The code organization can be used in most imperative language.

      +

      As Haskell is designed for functional paradigm, it is easier to use in this context. In reality, the firsts sections will use an imperative paradigm. As you can use functional paradigm in imperative language, you can also use imperative paradigm in functional languages.

      +

      This article is about creating an useful and clean program. It can interact with the user in real time. It uses OpenGL, a library with imperative programming foundations. Despite this fact, most of the final code will remain in the pure part (no IO).

      +

      I believe the main audience for this article are:

      +
        +
      • Haskell programmer looking for an OpengGL tutorial.
      • +
      • People interested in program organization (programming language agnostic).
      • +
      • Fractal lovers and in particular 3D fractal.
      • +
      • People interested in user interaction in a functional paradigm.
      • +
      +

      I had in mind for some time now to make a Mandelbrot set explorer. I had already written a command line Mandelbrot set generator in Haskell. This utility is highly parallel; it uses the repa package1.

      +

      This time, we will not parallelize the computation. Instead, we will display the Mandelbrot set extended in 3D using OpenGL and Haskell. You will be able to move it using your keyboard. This object is a Mandelbrot set in the plan (z=0), and something nice to see in 3D.

      +

      Here are some screenshots of the result:

      +
      +The entire Mandelbulb +
      +The entire Mandelbulb +
      +
      +A Mandelbulb detail +
      +A Mandelbulb detail +
      +
      +Another detail of the Mandelbulb +
      +Another detail of the Mandelbulb +
      + +

      And you can see the intermediate steps to reach this goal:

      +

      The parts of the article

      +

      From the 2nd section to the 4th it will be dirtier and dirtier. We start cleaning the code at the 5th section.

      +
      +

      Download the source code of this section → 01_Introduction/hglmandel.lhs

      +

      First version

      +

      We can consider two parts. The first being mostly some boilerplate2. And the second part more focused on OpenGL and content.

      +

      Let’s play the song of our people

      +
      +
      import Graphics.Rendering.OpenGL
      +import Graphics.UI.GLUT
      +import Data.IORef
      +
      + +

      For efficiency reason3, I will not use the default Haskell Complex data type.

      +
      +
      data Complex = C (Float,Float) deriving (Show,Eq)
      +
      + +
      +
      instance Num Complex where
      +    fromInteger n = C (fromIntegral n,0.0)
      +    C (x,y) * C (z,t) = C (z*x - y*t, y*z + x*t)
      +    C (x,y) + C (z,t) = C (x+z, y+t)
      +    abs (C (x,y))     = C (sqrt (x*x + y*y),0.0)
      +    signum (C (x,y))  = C (signum x , 0.0)
      +
      + +

      We declare some useful functions for manipulating complex numbers:

      +
      +
      complex :: Float -> Float -> Complex
      +complex x y = C (x,y)
      +
      +real :: Complex -> Float
      +real (C (x,y))    = x
      +
      +im :: Complex -> Float
      +im   (C (x,y))    = y
      +
      +magnitude :: Complex -> Float
      +magnitude = real.abs
      +
      + +

      Let us start

      +

      We start by giving the main architecture of our program:

      +
      +
      main :: IO ()
      +main = do
      +  -- GLUT need to be initialized
      +  (progname,_) <- getArgsAndInitialize
      +  -- We will use the double buffered mode (GL constraint)
      +  initialDisplayMode $= [DoubleBuffered]
      +  -- We create a window with some title
      +  createWindow "Mandelbrot Set with Haskell and OpenGL"
      +  -- Each time we will need to update the display
      +  -- we will call the function 'display'
      +  displayCallback $= display
      +  -- We enter the main loop
      +  mainLoop
      +
      + +

      Mainly, we initialize our OpenGL application. We declared that the function display will be used to render the graphics:

      +
      +
      display = do
      +  clear [ColorBuffer] -- make the window black
      +  loadIdentity -- reset any transformation
      +  preservingMatrix drawMandelbrot
      +  swapBuffers -- refresh screen
      +
      + +

      Also here, there is only one interesting line; the draw will occur in the function drawMandelbrot.

      +

      This function will provide a list of draw actions. Remember that OpenGL is imperative by design. Then, one of the consequence is you must write the actions in the right order. No easy parallel drawing here. Here is the function which will render something on the screen:

      +
      +
      drawMandelbrot =
      +  -- We will print Points (not triangles for example) 
      +  renderPrimitive Points $ do
      +    mapM_ drawColoredPoint allPoints
      +  where
      +      drawColoredPoint (x,y,c) = do
      +          color c -- set the current color to c
      +          -- then draw the point at position (x,y,0)
      +          -- remember we're in 3D
      +          vertex $ Vertex3 x y 0 
      +
      + +

      The mapM_ function is mainly the same as map but inside a monadic context. More precisely, this can be transformed as a list of actions where the order is important:

      +
      drawMandelbrot = 
      +  renderPrimitive Points $ do
      +    color color1
      +    vertex $ Vertex3 x1 y1 0
      +    ...
      +    color colorN
      +    vertex $ Vertex3 xN yN 0
      +

      We also need some kind of global variables. In fact, global variable are a proof of a design problem. We will get rid of them later.

      +
      +
      width = 320 :: GLfloat
      +height = 320 :: GLfloat
      +
      + +

      And of course our list of colored points. In OpenGL the default coordinate are from -1 to 1.

      +
      +
      allPoints :: [(GLfloat,GLfloat,Color3 GLfloat)]
      +allPoints = [ (x/width,y/height,colorFromValue $ mandel x y) | 
      +                  x <- [-width..width], 
      +                  y <- [-height..height]]
      +
      + +

      We need a function which transform an integer value to some color:

      +
      +
      colorFromValue n =
      +  let 
      +      t :: Int -> GLfloat
      +      t i = 0.5 + 0.5*cos( fromIntegral i / 10 )
      +  in
      +    Color3 (t n) (t (n+5)) (t (n+10))
      +
      + +

      And now the mandel function. Given two coordinates in pixels, it returns some integer value:

      +
      +
      mandel x y = 
      +  let r = 2.0 * x / width
      +      i = 2.0 * y / height
      +  in
      +      f (complex r i) 0 64
      +
      + +

      It uses the main Mandelbrot function for each complex \(c\). The Mandelbrot set is the set of complex number \(c\) such that the following sequence does not escape to infinity.

      +

      Let us define \(f_c: \)

      +


      fc(z) = z2 + c

      +

      The sequence is:

      +


      0 → fc(0) → fc(fc(0)) → ⋯ → fcn(0) → ⋯

      +

      Of course, instead of trying to test the real limit, we just make a test after a finite number of occurrences.

      +
      +
      f :: Complex -> Complex -> Int -> Int
      +f c z 0 = 0
      +f c z n = if (magnitude z > 2 ) 
      +          then n
      +          else f c ((z*z)+c) (n-1)
      +
      + +

      Well, if you download this file (look at the bottom of this section), compile it and run it this is the result:

      +

      The mandelbrot set version 1

      +

      A first very interesting property of this program is that the computation for all the points is done only once. It is a bit long before the first image appears, but if you resize the window, it updates instantaneously. This property is a direct consequence of purity. If you look closely, you see that allPoints is a pure list. Therefore, calling allPoints will always render the same result and Haskell is clever enough to use this property. While Haskell doesn’t garbage collect allPoints the result is reused for free. We did not specified this value should be saved for later use. It is saved for us.

      +

      See what occurs if we make the window bigger:

      +

      The mandelbrot too wide, black lines and columns

      +

      We see some black lines because we have drawn less point than there is on the surface. We can repair this by drawing little squares instead of just points. But, instead we will do something a bit different and unusual.

      +

      Download the source code of this section → 01_Introduction/hglmandel.lhs

      +
      +

      Download the source code of this section → 02_Edges/HGLMandelEdge.lhs

      +

      Only the edges

      +
      + +
      +
      import Graphics.Rendering.OpenGL
      +import Graphics.UI.GLUT
      +import Data.IORef
      +-- Use UNPACK data because it is faster
      +-- The ! is for strict instead of lazy
      +data Complex = C  {-# UNPACK #-} !Float 
      +                  {-# UNPACK #-} !Float 
      +               deriving (Show,Eq)
      +instance Num Complex where
      +    fromInteger n = C (fromIntegral n) 0.0
      +    (C x y) * (C z t) = C (z*x - y*t) (y*z + x*t)
      +    (C x y) + (C z t) = C (x+z) (y+t)
      +    abs (C x y)     = C (sqrt (x*x + y*y)) 0.0
      +    signum (C x y)  = C (signum x) 0.0
      +complex :: Float -> Float -> Complex
      +complex x y = C x y
      +
      +real :: Complex -> Float
      +real (C x y)    = x
      +
      +im :: Complex -> Float
      +im   (C x y)    = y
      +
      +magnitude :: Complex -> Float
      +magnitude = real.abs
      +main :: IO ()
      +main = do
      +  -- GLUT need to be initialized
      +  (progname,_) <- getArgsAndInitialize
      +  -- We will use the double buffered mode (GL constraint)
      +  initialDisplayMode $= [DoubleBuffered]
      +  -- We create a window with some title
      +  createWindow "Mandelbrot Set with Haskell and OpenGL"
      +  -- Each time we will need to update the display
      +  -- we will call the function 'display'
      +  displayCallback $= display
      +  -- We enter the main loop
      +  mainLoop
      +display = do
      +   -- set the background color (dark solarized theme)
      +  clearColor $= Color4 0 0.1686 0.2117 1
      +  clear [ColorBuffer] -- make the window black
      +  loadIdentity -- reset any transformation
      +  preservingMatrix drawMandelbrot
      +  swapBuffers -- refresh screen
      +
      +width = 320 :: GLfloat
      +height = 320 :: GLfloat
      +
      + +
      + +

      This time, instead of drawing all points, we will simply draw the edges of the Mandelbrot set. The method I use is a rough approximation. I consider the Mandelbrot set to be almost convex. The result will be good enough for the purpose of this tutorial.

      +

      We change slightly the drawMandelbrot function. We replace the Points by LineLoop

      +
      +
      drawMandelbrot =
      +  -- We will print Points (not triangles for example) 
      +  renderPrimitive LineLoop $ do
      +    mapM_ drawColoredPoint allPoints
      +  where
      +      drawColoredPoint (x,y,c) = do
      +          color c -- set the current color to c
      +          -- then draw the point at position (x,y,0)
      +          -- remember we're in 3D
      +          vertex $ Vertex3 x y 0 
      +
      + +

      And now, we should change our list of points. Instead of drawing every point of the visible surface, we will choose only point on the surface.

      +
      +
      allPoints = positivePoints ++ 
      +      map (\(x,y,c) -> (x,-y,c)) (reverse positivePoints)
      +
      + +

      We only need to compute the positive point. The Mandelbrot set is symmetric relatively to the abscisse axis.

      +
      +
      positivePoints :: [(GLfloat,GLfloat,Color3 GLfloat)]
      +positivePoints = do
      +     x <- [-width..width]
      +     let y = maxZeroIndex (mandel x) 0 height (log2 height)
      +     if y < 1 -- We don't draw point in the absciss
      +        then []
      +        else return (x/width,y/height,colorFromValue $ mandel x y)
      +     where
      +         log2 n = floor ((log n) / log 2)
      +
      + +

      This function is interesting. For those not used to the list monad here is a natural language version of this function:

      +
      positivePoints =
      +    for all x in the range [-width..width]
      +    let y be smallest number s.t. mandel x y > 0
      +    if y is on 0 then don't return a point
      +    else return the value corresonding to (x,y,color for (x+iy))
      +

      In fact using the list monad you write like if you consider only one element at a time and the computation is done non deterministically. To find the smallest number such that mandel x y > 0 we use a simple dichotomy:

      +
      +
      -- given f min max nbtest,
      +-- considering 
      +--  - f is an increasing function
      +--  - f(min)=0
      +--  - f(max)≠0
      +-- then maxZeroIndex f min max nbtest returns x such that
      +--    f(x - ε)=0 and f(x + ε)≠0
      +--    where ε=(max-min)/2^(nbtest+1) 
      +maxZeroIndex func minval maxval 0 = (minval+maxval)/2
      +maxZeroIndex func minval maxval n = 
      +  if (func medpoint) /= 0 
      +       then maxZeroIndex func minval medpoint (n-1)
      +       else maxZeroIndex func medpoint maxval (n-1)
      +  where medpoint = (minval+maxval)/2
      +
      + +

      No rocket science here. See the result now:

      +

      The edges of the mandelbrot set

      +
      + +
      +
      colorFromValue n =
      +  let 
      +      t :: Int -> GLfloat
      +      t i = 0.5 + 0.5*cos( fromIntegral i / 10 )
      +  in
      +    Color3 (t n) (t (n+5)) (t (n+10))
      +
      + +
      +
      mandel x y = 
      +  let r = 2.0 * x / width
      +      i = 2.0 * y / height
      +  in
      +      f (complex r i) 0 64
      +
      + +
      +
      f :: Complex -> Complex -> Int -> Int
      +f c z 0 = 0
      +f c z n = if (magnitude z > 2 ) 
      +          then n
      +          else f c ((z*z)+c) (n-1)
      +
      + +
      + +

      Download the source code of this section → 02_Edges/HGLMandelEdge.lhs

      +
      +

      Download the source code of this section → 03_Mandelbulb/Mandelbulb.lhs

      +

      3D Mandelbrot?

      +

      Now we will we extend to a third dimension. But, there is no 3D equivalent to complex. In fact, the only extension known are quaternions (in 4D). As I know almost nothing about quaternions, I will use some extended complex, instead of using a 3D projection of quaternions. I am pretty sure this construction is not useful for numbers. But it will be enough for us to create something that look nice.

      +

      This section is quite long, but don’t be afraid, most of the code is some OpenGL boilerplate. If you just want to skim this section, here is a high level representation:

      +
      +
        +
      • OpenGL Boilerplate

      • +
      • set some IORef (understand variables) for states
      • +
      • Drawing:

        +
          +
        • set doubleBuffer, handle depth, window size…
        • +
        • Use state to apply some transformations
        • +
      • +
      • Keyboard: hitting some key change the state of IORef

      • +
      • Generate 3D Object

      • +
      +

      ~ allPoints :: [ColoredPoint]
      allPoints = for all (x,y), -width 0 add the points (x, y, z,color) (x,-y, z,color) (x, y,-z,color) (x,-y,-z,color) + neighbors to make triangles ~

      +
      +
      + +
      +
      import Graphics.Rendering.OpenGL
      +import Graphics.UI.GLUT
      +import Data.IORef
      +type ColoredPoint = (GLfloat,GLfloat,GLfloat,Color3 GLfloat)
      +
      + +
      + +

      We declare a new type ExtComplex (for extended complex). An extension of complex numbers with a third component:

      +
      +
      data ExtComplex = C (GLfloat,GLfloat,GLfloat) 
      +                  deriving (Show,Eq)
      +instance Num ExtComplex where
      +    -- The shape of the 3D mandelbrot 
      +    -- will depend on this formula
      +    C (x,y,z) * C (x',y',z') = C (x*x' - y*y' - z*z', 
      +                                  x*y' + y*x' + z*z', 
      +                                  x*z' + z*x' )
      +    -- The rest is straightforward
      +    fromInteger n = C (fromIntegral n, 0, 0)
      +    C (x,y,z) + C (x',y',z') = C (x+x', y+y', z+z')
      +    abs (C (x,y,z))     = C (sqrt (x*x + y*y + z*z), 0, 0)
      +    signum (C (x,y,z))  = C (signum x, signum y, signum z)
      +
      + +

      The most important part is the new multiplication instance. Modifying this formula will change radically the shape of the result. Here is the formula written in a more mathematical notation. I called the third component of these extended complex strange.

      +


      real((x, y, z) * (xʹ, yʹ, zʹ)) = xxʹ − yyʹ − zzʹ

      +


      im((x, y, z) * (xʹ, yʹ, zʹ)) = xyʹ − yxʹ + zzʹ

      +


      strange((x, y, z) * (xʹ, yʹ, zʹ)) = xzʹ + zxʹ

      +

      Note how if z=z'=0 then the multiplication is the same to the complex one.

      +
      + +
      +
      extcomplex :: GLfloat -> GLfloat -> GLfloat -> ExtComplex
      +extcomplex x y z = C (x,y,z)
      +
      +real :: ExtComplex -> GLfloat
      +real (C (x,y,z))    = x
      +
      +im :: ExtComplex -> GLfloat
      +im   (C (x,y,z))    = y
      +
      +strange :: ExtComplex -> GLfloat
      +strange (C (x,y,z)) = z
      +
      +magnitude :: ExtComplex -> GLfloat
      +magnitude = real.abs
      +
      + +
      + +

      From 2D to 3D

      +

      As we will use some 3D, we add some new directive in the boilerplate. But mainly, we simply state that will use some depth buffer. And also we will listen the keyboard.

      +
      +
      main :: IO ()
      +main = do
      +  -- GLUT need to be initialized
      +  (progname,_) <- getArgsAndInitialize
      +  -- We will use the double buffered mode (GL constraint)
      +  -- We also Add the DepthBuffer (for 3D)
      +  initialDisplayMode $= 
      +      [WithDepthBuffer,DoubleBuffered,RGBMode]
      +  -- We create a window with some title
      +  createWindow "3D HOpengGL Mandelbrot"
      +  -- We add some directives
      +  depthFunc  $= Just Less
      +  windowSize $= Size 500 500
      +  -- Some state variables (I know it feels BAD)
      +  angle   <- newIORef ((35,0)::(GLfloat,GLfloat))
      +  zoom    <- newIORef (2::GLfloat)
      +  campos  <- newIORef ((0.7,0)::(GLfloat,GLfloat))
      +  -- Function to call each frame
      +  idleCallback $= Just idle
      +  -- Function to call when keyboard or mouse is used
      +  keyboardMouseCallback $= 
      +          Just (keyboardMouse angle zoom campos)
      +  -- Each time we will need to update the display
      +  -- we will call the function 'display'
      +  -- But this time, we add some parameters
      +  displayCallback $= display angle zoom campos
      +  -- We enter the main loop
      +  mainLoop
      +
      + +

      The idle is here to change the states. There should never be any modification done in the display function.

      +
      +
      idle = postRedisplay Nothing
      +
      + +

      We introduce some helper function to manipulate standard IORef. Mainly modVar x f is equivalent to the imperative x:=f(x), modFst (x,y) (+1) is equivalent to (x,y) := (x+1,y) and modSnd (x,y) (+1) is equivalent to (x,y) := (x,y+1)

      +
      +
      modVar v f = do
      +  v' <- get v
      +  v $= (f v')
      +mapFst f (x,y) = (f x,  y)
      +mapSnd f (x,y) = (  x,f y)
      +
      + +

      And we use them to code the function handling keyboard. We will use the keys hjkl to rotate, oi to zoom and sedf to move. Also, hitting space will reset the view. Remember that angle and campos are pairs and zoom is a scalar. Also note (+0.5) is the function \x->x+0.5 and (-0.5) is the number -0.5 (yes I share your pain).

      +
      +
      keyboardMouse angle zoom campos key state modifiers position =
      +  -- We won't use modifiers nor position
      +  kact angle zoom campos key state
      +  where 
      +    -- reset view when hitting space
      +    kact a z p (Char ' ') Down = do
      +          a $= (0,0) -- angle 
      +          z $= 1     -- zoom
      +          p $= (0,0) -- camera position
      +    -- use of hjkl to rotate
      +    kact a _ _ (Char 'h') Down = modVar a (mapFst (+0.5))
      +    kact a _ _ (Char 'l') Down = modVar a (mapFst (+(-0.5)))
      +    kact a _ _ (Char 'j') Down = modVar a (mapSnd (+0.5))
      +    kact a _ _ (Char 'k') Down = modVar a (mapSnd (+(-0.5)))
      +    -- use o and i to zoom
      +    kact _ z _ (Char 'o') Down = modVar z (*1.1)
      +    kact _ z _ (Char 'i') Down = modVar z (*0.9)
      +    -- use sdfe to move the camera
      +    kact _ _ p (Char 's') Down = modVar p (mapFst (+0.1))
      +    kact _ _ p (Char 'f') Down = modVar p (mapFst (+(-0.1)))
      +    kact _ _ p (Char 'd') Down = modVar p (mapSnd (+0.1))
      +    kact _ _ p (Char 'e') Down = modVar p (mapSnd (+(-0.1)))
      +    -- any other keys does nothing
      +    kact _ _ _ _ _ = return ()
      +
      + +

      Note display takes some parameters this time. This function if full of boilerplate:

      +
      +
      display angle zoom position = do
      +   -- set the background color (dark solarized theme)
      +  clearColor $= Color4 0 0.1686 0.2117 1
      +  clear [ColorBuffer,DepthBuffer]
      +  -- Transformation to change the view
      +  loadIdentity -- reset any transformation
      +  -- tranlate
      +  (x,y) <- get position
      +  translate $ Vector3 x y 0 
      +  -- zoom
      +  z <- get zoom
      +  scale z z z
      +  -- rotate
      +  (xangle,yangle) <- get angle
      +  rotate xangle $ Vector3 1.0 0.0 (0.0::GLfloat)
      +  rotate yangle $ Vector3 0.0 1.0 (0.0::GLfloat)
      +
      +  -- Now that all transformation were made
      +  -- We create the object(s)
      +  preservingMatrix drawMandelbrot
      +
      +  swapBuffers -- refresh screen
      +
      + +

      Not much to say about this function. Mainly there are two parts: apply some transformations, draw the object.

      +

      The 3D Mandelbrot

      +

      We have finished with the OpenGL section, let’s talk about how we generate the 3D points and colors. First, we will set the number of details to 200 pixels in the three dimensions.

      +
      +
      nbDetails = 200 :: GLfloat
      +width  = nbDetails
      +height = nbDetails
      +deep   = nbDetails
      +
      + +

      This time, instead of just drawing some line or some group of points, we will show triangles. The function allPoints will provide a multiple of three points. Each three successive point representing the coordinate of each vertex of a triangle.

      +
      +
      drawMandelbrot = do
      +  -- We will print Points (not triangles for example) 
      +  renderPrimitive Triangles $ do
      +    mapM_ drawColoredPoint allPoints
      +  where
      +      drawColoredPoint (x,y,z,c) = do
      +          color c
      +          vertex $ Vertex3 x y z
      +
      + +

      In fact, we will provide six ordered points. These points will be used to draw two triangles.

      +

      Explain triangles

      +

      The next function is a bit long. Here is an approximative English version:

      +
      forall x from -width to width
      +  forall y from -height to height
      +    forall the neighbors of (x,y)
      +      let z be the smalled depth such that (mandel x y z)>0
      +      let c be the color given by mandel x y z 
      +      add the point corresponding to (x,y,z,c)
      +

      Also, I added a test to hide points too far from the border. In fact, this function show points close to the surface of the modified mandelbrot set. But not the mandelbrot set itself.

      +
      depthPoints :: [ColoredPoint]
      +depthPoints = do
      +  x <- [-width..width]
      +  y <- [-height..height]
      +  let 
      +      depthOf x' y' = maxZeroIndex (mandel x' y') 0 deep logdeep 
      +      logdeep = floor ((log deep) / log 2)
      +      z1 = depthOf    x     y
      +      z2 = depthOf (x+1)    y
      +      z3 = depthOf (x+1) (y+1)
      +      z4 = depthOf    x  (y+1)
      +      c1 = mandel    x     y  (z1+1)
      +      c2 = mandel (x+1)    y  (z2+1)
      +      c3 = mandel (x+1) (y+1) (z3+1)
      +      c4 = mandel    x  (y+1) (z4+1)
      +      p1 = (   x /width,   y /height, z1/deep, colorFromValue c1)
      +      p2 = ((x+1)/width,   y /height, z2/deep, colorFromValue c2)
      +      p3 = ((x+1)/width,(y+1)/height, z3/deep, colorFromValue c3)
      +      p4 = (   x /width,(y+1)/height, z4/deep, colorFromValue c4)
      +  if (and $ map (>=57) [c1,c2,c3,c4])
      +  then []
      +  else [p1,p2,p3,p1,p3,p4]
      +

      If you look at the function above, you see a lot of common patterns. Haskell is very efficient to make this better. Here is a harder to read but shorter and more generic rewritten function:

      +
      +
      depthPoints :: [ColoredPoint]
      +depthPoints = do
      +  x <- [-width..width]
      +  y <- [-height..height]
      +  let 
      +    neighbors = [(x,y),(x+1,y),(x+1,y+1),(x,y+1)]
      +    depthOf (u,v) = maxZeroIndex (mandel u v) 0 deep logdeep
      +    logdeep = floor ((log deep) / log 2)
      +    -- zs are 3D points with found depth
      +    zs = map (\(u,v) -> (u,v,depthOf (u,v))) neighbors
      +    -- ts are 3D pixels + mandel value
      +    ts = map (\(u,v,w) -> (u,v,w,mandel u v (w+1))) zs
      +    -- ps are 3D opengl points + color value
      +    ps = map (\(u,v,w,c') -> 
      +        (u/width,v/height,w/deep,colorFromValue c')) ts
      +  -- If the point diverged too fast, don't display it
      +  if (and $ map (\(_,_,_,c) -> c>=57) ts)
      +  then []
      +  -- Draw two triangles
      +  else [ps!!0,ps!!1,ps!!2,ps!!0,ps!!2,ps!!3]
      +
      + +

      If you prefer the first version, then just imagine how hard it will be to change the enumeration of the point from (x,y) to (x,z) for example.

      +

      Also, we didn’t searched for negative values. This modified Mandelbrot is no more symmetric relatively to the plan y=0. But it is symmetric relatively to the plan z=0. Then I mirror these values.

      +
      +
      allPoints :: [ColoredPoint]
      +allPoints = planPoints ++ map inverseDepth  planPoints
      +  where 
      +      planPoints = depthPoints
      +      inverseDepth (x,y,z,c) = (x,y,-z+1/deep,c)
      +
      + +

      The rest of the program is very close to the preceding one.

      +
      + +
      +
      -- given f min max nbtest,
      +-- considering 
      +--  - f is an increasing function
      +--  - f(min)=0
      +--  - f(max)≠0
      +-- then maxZeroIndex f min max nbtest returns x such that
      +--    f(x - ε)=0 and f(x + ε)≠0
      +--    where ε=(max-min)/2^(nbtest+1) 
      +maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => 
      +                 (a -> b) -> a -> a -> Int -> a
      +maxZeroIndex func minval maxval 0 = (minval+maxval)/2
      +maxZeroIndex func minval maxval n = 
      +  if (func medpoint) /= 0 
      +       then maxZeroIndex func minval medpoint (n-1)
      +       else maxZeroIndex func medpoint maxval (n-1)
      +  where medpoint = (minval+maxval)/2
      +
      + +

      I made the color slightly brighter

      +
      +
      colorFromValue n =
      +  let 
      +      t :: Int -> GLfloat
      +      t i = 0.7 + 0.3*cos( fromIntegral i / 10 )
      +  in
      +    Color3 (t n) (t (n+5)) (t (n+10))
      +
      + +

      We only changed from Complex to ExtComplex of the main f function.

      +
      +
      f :: ExtComplex -> ExtComplex -> Int -> Int
      +f c z 0 = 0
      +f c z n = if (magnitude z > 2 ) 
      +          then n
      +          else f c ((z*z)+c) (n-1)
      +
      + +
      + +

      We simply add a new dimension to the mandel function and change the type signature of f from Complex to ExtComplex.

      +
      +
      mandel x y z = 
      +  let r = 2.0 * x / width
      +      i = 2.0 * y / height
      +      s = 2.0 * z / deep
      +  in
      +      f (extcomplex r i s) 0 64
      +
      + +

      Here is the result:

      +

      A 3D mandelbrot like

      +

      Download the source code of this section → 03_Mandelbulb/Mandelbulb.lhs

      +
      +

      Download the source code of this section → 04_Mandelbulb/Mandelbulb.lhs

      +

      Naïve code cleaning

      +

      The first approach to clean the code is to separate the GLUT/OpenGL part from the computation of the shape. Here is the cleaned version of the preceding section. Most boilerplate was put in external files.

      + +
      +
      import YBoiler -- Most the OpenGL Boilerplate
      +import Mandel -- The 3D Mandelbrot maths
      +
      + +

      The yMainLoop takes two arguments: the title of the window and a function from time to triangles

      +
      +
      main :: IO ()
      +main = yMainLoop "3D Mandelbrot" (\_ -> allPoints)
      +
      + +

      We set some global constant (this is generally bad).

      +
      +
      nbDetails = 200 :: GLfloat
      +width  = nbDetails
      +height = nbDetails
      +deep   = nbDetails
      +
      + +

      We then generate colored points from our function. This is similar to the preceding section.

      +
      +
      allPoints :: [ColoredPoint]
      +allPoints = planPoints ++ map inverseDepth  planPoints
      +  where 
      +      planPoints = depthPoints ++ map inverseHeight depthPoints
      +      inverseHeight (x,y,z,c) = (x,-y,z,c)
      +      inverseDepth (x,y,z,c) = (x,y,-z+1/deep,c)
      +
      + +
      +
      depthPoints :: [ColoredPoint]
      +depthPoints = do
      +  x <- [-width..width]
      +  y <- [0..height]
      +  let 
      +    neighbors = [(x,y),(x+1,y),(x+1,y+1),(x,y+1)]
      +    depthOf (u,v) = maxZeroIndex (ymandel u v) 0 deep 7
      +    -- zs are 3D points with found depth
      +    zs = map (\(u,v) -> (u,v,depthOf (u,v))) neighbors
      +    -- ts are 3D pixels + mandel value
      +    ts = map (\(u,v,w) -> (u,v,w,ymandel u v (w+1))) zs
      +    -- ps are 3D opengl points + color value
      +    ps = map (\(u,v,w,c') -> 
      +        (u/width,v/height,w/deep,colorFromValue c')) ts
      +  -- If the point diverged too fast, don't display it
      +  if (and $ map (\(_,_,_,c) -> c>=57) ts)
      +  then []
      +  -- Draw two triangles
      +  else [ps!!0,ps!!1,ps!!2,ps!!0,ps!!2,ps!!3]
      +
      +-- given f min max nbtest,
      +-- considering 
      +--  - f is an increasing function
      +--  - f(min)=0
      +--  - f(max)≠0
      +-- then maxZeroIndex f min max nbtest returns x such that
      +--    f(x - ε)=0 and f(x + ε)≠0
      +--    where ε=(max-min)/2^(nbtest+1) 
      +maxZeroIndex func minval maxval 0 = (minval+maxval)/2
      +maxZeroIndex func minval maxval n = 
      +  if (func medpoint) /= 0 
      +       then maxZeroIndex func minval medpoint (n-1)
      +       else maxZeroIndex func medpoint maxval (n-1)
      +  where medpoint = (minval+maxval)/2
      +
      +colorFromValue n =
      +  let 
      +      t :: Int -> GLfloat
      +      t i = 0.7 + 0.3*cos( fromIntegral i / 10 )
      +  in
      +    ((t n),(t (n+5)),(t (n+10)))
      +
      +ymandel x y z = mandel (2*x/width) (2*y/height) (2*z/deep) 64
      +
      + +

      This code is cleaner but many things doesn’t feel right. First, all the user interaction code is outside our main file. I feel it is okay to hide the detail for the rendering. But I would have preferred to control the user actions.

      +

      On the other hand, we continue to handle a lot rendering details. For example, we provide ordered vertices.

      +

      Download the source code of this section → 04_Mandelbulb/Mandelbulb.lhs

      +
      +

      Download the source code of this section → 05_Mandelbulb/Mandelbulb.lhs

      +

      Functional organization?

      +

      Some points:

      +
        +
      1. OpenGL and GLUT is done in C. In particular the mainLoop function is a direct link to the C library (FFI). This function is clearly far from the functional paradigm. Could we make this better? We will have two choices:
      2. +
      +
        +
      • create our own mainLoop function to make it more functional.
      • +
      • deal with the imperative nature of the GLUT mainLoop function.
      • +
      +

      As one of the goal of this article is to understand how to deal with existing libraries and particularly the one coming from imperative languages, we will continue to use the mainLoop function. 2. Our main problem come from user interaction. If you ask “the Internet”, about how to deal with user interaction with a functional paradigm, the main answer is to use functional reactive programming (FRP). I won’t use FRP in this article. Instead, I’ll use a simpler while less effective way to deal with user interaction. But The method I’ll use will be as pure and functional as possible.

      +

      Here is how I imagine things should go. First, what the main loop should look like if we could make our own:

      +
      functionalMainLoop =
      +    Read user inputs and provide a list of actions
      +    Apply all actions to the World
      +    Display one frame 
      +    repetere aeternum
      +

      Clearly, ideally we should provide only three parameters to this main loop function:

      +
        +
      • an initial World state
      • +
      • a mapping between the user interactions and functions which modify the world
      • +
      • a function taking two parameters: time and world state and render a new world without user interaction.
      • +
      +

      Here is a real working code, I’ve hidden most display functions. The YGL, is a kind of framework to display 3D functions. But it can easily be extended to many kind of representation.

      +
      +
      import YGL -- Most the OpenGL Boilerplate
      +import Mandel -- The 3D Mandelbrot maths
      +
      + +

      We first set the mapping between user input and actions. The type of each couple should be of the form (user input, f) where (in a first time) f:World -> World. It means, the user input will transform the world state.

      +
      +
      -- Centralize all user input interaction
      +inputActionMap :: InputMap World
      +inputActionMap = inputMapFromList [
      +     (Press 'k' , rotate xdir   5)
      +    ,(Press 'i' , rotate xdir (-5))
      +    ,(Press 'j' , rotate ydir   5)
      +    ,(Press 'l' , rotate ydir (-5))
      +    ,(Press 'o' , rotate zdir   5)
      +    ,(Press 'u' , rotate zdir (-5))
      +    ,(Press 'f' , translate xdir   0.1)
      +    ,(Press 's' , translate xdir (-0.1))
      +    ,(Press 'e' , translate ydir   0.1)
      +    ,(Press 'd' , translate ydir (-0.1))
      +    ,(Press 'z' , translate zdir   0.1)
      +    ,(Press 'r' , translate zdir (-0.1))
      +    ,(Press '+' , zoom    1.1)
      +    ,(Press '-' , zoom (1/1.1))
      +    ,(Press 'h' , resize    1.2)
      +    ,(Press 'g' , resize (1/1.2))
      +    ]
      +
      + +

      And of course a type design the World State. The important part is that it is our World State type. We could have used any kind of data type.

      +
      +
      -- I prefer to set my own name for these types
      +data World = World {
      +      angle       :: Point3D
      +    , scale       :: Scalar
      +    , position    :: Point3D
      +    , shape       :: Scalar -> Function3D
      +    , box         :: Box3D
      +    , told        :: Time -- last frame time
      +    } 
      +
      + +

      The important part to glue our own type to the framework is to make our type an instance of the type class DisplayableWorld. We simply have to provide the definition of some functions.

      +
      +
      instance DisplayableWorld World where
      +  winTitle _ = "The YGL Mandelbulb"
      +  camera w = Camera {
      +        camPos = position w, 
      +        camDir = angle w,
      +        camZoom = scale w }
      +  -- objects for world w
      +  -- is the list of one unique element
      +  -- The element is an YObject
      +  --   more precisely the XYFunc Function3D Box3D
      +  --   where the Function3D is the type
      +  --             Point -> Point -> Maybe (Point,Color)
      +  --   and its value here is ((shape w) res)
      +  --   and the Box3D value is defbox
      +  objects w = [XYFunc ((shape  w) res) defbox]
      +              where
      +                  res = resolution $ box w
      +                  defbox = box w
      +
      + +

      The camera function will retrieve an object of type Camera which contains most necessary information to set our camera. The objects function will returns a list of objects. Their type is YObject. Note the generation of triangles is no more in this file. Until here we only used declarative pattern.

      +

      We also need to set all our transformation functions. These function are used to update the world state.

      +
      +
      xdir :: Point3D
      +xdir = makePoint3D (1,0,0)
      +ydir :: Point3D
      +ydir = makePoint3D (0,1,0)
      +zdir :: Point3D
      +zdir = makePoint3D (0,0,1)
      +
      + +

      Note (-*<) is the scalar product (α -*< (x,y,z) = (αx,αy,αz)). Also note we could add two Point3D.

      +
      +
      rotate :: Point3D -> Scalar -> World -> World
      +rotate dir angleValue world = 
      +  world {
      +     angle = (angle world) + (angleValue -*< dir) }
      +
      +translate :: Point3D -> Scalar -> World -> World
      +translate dir len world = 
      +  world {
      +    position = (position world) + (len -*< dir) }
      +
      +zoom :: Scalar -> World -> World
      +zoom z world = world {
      +    scale = z * scale world }
      +
      +resize :: Scalar -> World -> World
      +resize r world = world {
      +    box = (box world) {
      +     resolution = sqrt ((resolution (box world))**2 * r) }}
      +
      + +

      The resize is used to generate the 3D function. As I wanted the time spent to generate a more detailed view to grow linearly I use this not so straightforward formula.

      +

      The yMainLoop takes three arguments.

      +
        +
      • A map between user Input and world transformation
      • +
      • A timed world transformation
      • +
      • An initial world state
      • +
      +
      +
      main :: IO ()
      +main = yMainLoop inputActionMap idleAction initialWorld
      +
      + +

      Here is our initial world state.

      +
      +
      -- We initialize the world state
      +-- then angle, position and zoom of the camera
      +-- And the shape function
      +initialWorld :: World
      +initialWorld = World {
      +   angle = makePoint3D (-30,-30,0)
      + , position = makePoint3D (0,0,0)
      + , scale = 0.8
      + , shape = shapeFunc 
      + , box = Box3D { minPoint = makePoint3D (-2,-2,-2)
      +               , maxPoint =  makePoint3D (2,2,2)
      +               , resolution =  0.16 }
      + , told = 0
      + }
      +
      + +

      We will define shapeFunc later. Here is the function which transform the world even without user action. Mainly it makes some rotation.

      +
      +
      idleAction :: Time -> World -> World
      +idleAction tnew world = world {
      +    angle = (angle world) + (delta -*< zdir)
      +  , told = tnew
      +  }
      +  where 
      +      anglePerSec = 5.0
      +      delta = anglePerSec * elapsed / 1000.0
      +      elapsed = fromIntegral (tnew - (told world))
      +
      + +

      Now the function which will generate points in 3D. The first parameter (res) is the resolution of the vertex generation. More precisely, res is distance between two points on one direction. We need it to “close” our shape.

      +

      The type Function3D is Point -> Point -> Maybe Point. Because we consider partial functions (for some (x,y) our function can be undefined).

      +
      +
      shapeFunc :: Scalar -> Function3D
      +shapeFunc res x y = 
      +  let 
      +      z = maxZeroIndex (ymandel x y) 0 1 20
      +  in
      +  if and [ maxZeroIndex (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 |
      +              val <- [res], xeps <- [-val,val], yeps<-[-val,val]]
      +      then Nothing 
      +      else Just (z,colorFromValue ((ymandel x y z) * 64))
      +
      + +

      With the color function.

      +
      +
      colorFromValue :: Point -> Color
      +colorFromValue n =
      +  let 
      +      t :: Point -> Scalar
      +      t i = 0.7 + 0.3*cos( i / 10 )
      +  in
      +    makeColor (t n) (t (n+5)) (t (n+10))
      +
      + +

      The rest is similar to the preceding sections.

      +
      +
      -- given f min max nbtest,
      +-- considering 
      +--  - f is an increasing function
      +--  - f(min)=0
      +--  - f(max)≠0
      +-- then maxZeroIndex f min max nbtest returns x such that
      +--    f(x - ε)=0 and f(x + ε)≠0
      +--    where ε=(max-min)/2^(nbtest+1) 
      +maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => 
      +                 (a -> b) -> a -> a -> Int -> a
      +maxZeroIndex _ minval maxval 0 = (minval+maxval)/2
      +maxZeroIndex func minval maxval n = 
      +  if (func medpoint) /= 0 
      +       then maxZeroIndex func minval medpoint (n-1)
      +       else maxZeroIndex func medpoint maxval (n-1)
      +  where medpoint = (minval+maxval)/2
      +
      +ymandel :: Point -> Point -> Point -> Point
      +ymandel x y z = fromIntegral (mandel x y z 64) / 64
      +
      + +

      I won’t explain how the magic occurs here. If you are interested, just read the file YGL.hs. It is commented a lot.

      + +

      Download the source code of this section → 05_Mandelbulb/Mandelbulb.lhs

      +
      +

      Download the source code of this section → 06_Mandelbulb/Mandelbulb.lhs

      +

      Optimization

      +

      Our code architecture feel very clean. All the meaningful code is in our main file and all display details are externalized. If you read the code of YGL.hs, you’ll see I didn’t made everything perfect. For example, I didn’t finished the code of the lights. But I believe it is a good first step and it will be easy to go further. Unfortunately the program of the preceding session is extremely slow. We compute the Mandelbulb for each frame now.

      +

      Before our program structure was:

      +
      Constant Function -> Constant List of Triangles -> Display
      +

      Now we have

      +
      Main loop -> World -> Function -> List of Objects -> Atoms -> Display
      +

      The World state could change. The compiler can no more optimize the computation for us. We have to manually explain when to redraw the shape.

      +

      To optimize we must do some things in a lower level. Mostly the program remains the same, but it will provide the list of atoms directly.

      +
      + +
      +
      import YGL -- Most the OpenGL Boilerplate
      +import Mandel -- The 3D Mandelbrot maths
      +
      +-- Centralize all user input interaction
      +inputActionMap :: InputMap World
      +inputActionMap = inputMapFromList [
      +     (Press ' ' , switchRotation)
      +    ,(Press 'k' , rotate xdir 5)
      +    ,(Press 'i' , rotate xdir (-5))
      +    ,(Press 'j' , rotate ydir 5)
      +    ,(Press 'l' , rotate ydir (-5))
      +    ,(Press 'o' , rotate zdir 5)
      +    ,(Press 'u' , rotate zdir (-5))
      +    ,(Press 'f' , translate xdir 0.1)
      +    ,(Press 's' , translate xdir (-0.1))
      +    ,(Press 'e' , translate ydir 0.1)
      +    ,(Press 'd' , translate ydir (-0.1))
      +    ,(Press 'z' , translate zdir 0.1)
      +    ,(Press 'r' , translate zdir (-0.1))
      +    ,(Press '+' , zoom 1.1)
      +    ,(Press '-' , zoom (1/1.1))
      +    ,(Press 'h' , resize 2.0)
      +    ,(Press 'g' , resize (1/2.0))
      +    ]
      +
      + +
      + +
      +
      data World = World {
      +      angle       :: Point3D
      +    , anglePerSec :: Scalar
      +    , scale       :: Scalar
      +    , position    :: Point3D
      +    , box         :: Box3D
      +    , told        :: Time 
      +    -- We replace shape by cache
      +    , cache       :: [YObject]
      +    } 
      +
      + +
      +
      instance DisplayableWorld World where
      +  winTitle _ = "The YGL Mandelbulb"
      +  camera w = Camera {
      +        camPos = position w, 
      +        camDir = angle w,
      +        camZoom = scale w }
      +  -- We update our objects instanciation
      +  objects = cache
      +
      + +
      + +
      +
      xdir :: Point3D
      +xdir = makePoint3D (1,0,0)
      +ydir :: Point3D
      +ydir = makePoint3D (0,1,0)
      +zdir :: Point3D
      +zdir = makePoint3D (0,0,1)
      +
      +rotate :: Point3D -> Scalar -> World -> World
      +rotate dir angleValue world = 
      +  world {
      +     angle = angle world + (angleValue -*< dir) }
      +
      +switchRotation :: World -> World
      +switchRotation world = 
      +  world {
      +     anglePerSec = if anglePerSec world > 0 then 0 else 5.0 }
      +
      +translate :: Point3D -> Scalar -> World -> World
      +translate dir len world = 
      +  world {
      +    position = position world + (len -*< dir) }
      +
      +zoom :: Scalar -> World -> World
      +zoom z world = world {
      +    scale = z * scale world }
      +
      + +
      +
      main :: IO ()
      +main = yMainLoop inputActionMap idleAction initialWorld
      +
      + +
      + +

      Our initial world state is slightly changed:

      +
      +
      -- We initialize the world state
      +-- then angle, position and zoom of the camera
      +-- And the shape function
      +initialWorld :: World
      +initialWorld = World {
      +   angle = makePoint3D (30,30,0)
      + , anglePerSec = 5.0
      + , position = makePoint3D (0,0,0)
      + , scale = 1.0
      + , box = Box3D { minPoint = makePoint3D (0-eps, 0-eps, 0-eps)
      +               , maxPoint = makePoint3D (0+eps, 0+eps, 0+eps)
      +               , resolution =  0.02 }
      + , told = 0
      + -- We declare cache directly this time
      + , cache = objectFunctionFromWorld initialWorld
      + }
      + where eps=2
      +
      + +

      The use of eps is a hint to make a better zoom by computing with the right bounds.

      +

      We use the YGL.getObject3DFromShapeFunction function directly. This way instead of providing XYFunc, we provide directly a list of Atoms.

      +
      +
      objectFunctionFromWorld :: World -> [YObject]
      +objectFunctionFromWorld w = [Atoms atomList]
      +  where atomListPositive = 
      +          getObject3DFromShapeFunction
      +              (shapeFunc (resolution (box w))) (box w)
      +        atomList = atomListPositive ++ 
      +          map negativeTriangle atomListPositive
      +        negativeTriangle (ColoredTriangle (p1,p2,p3,c)) = 
      +              ColoredTriangle (negz p1,negz p3,negz p2,c)
      +              where negz (P (x,y,z)) = P (x,y,-z)
      +
      + +

      We know that resize is the only world change that necessitate to recompute the list of atoms (triangles). Then we update our world state accordingly.

      +
      +
      resize :: Scalar -> World -> World
      +resize r world = 
      +  tmpWorld { cache = objectFunctionFromWorld tmpWorld }
      +  where 
      +      tmpWorld = world { box = (box world) {
      +              resolution = sqrt ((resolution (box world))**2 * r) }}
      +
      + +

      All the rest is exactly the same.

      +
      + +
      +
      idleAction :: Time -> World -> World
      +idleAction tnew world = 
      +      world {
      +        angle = angle world + (delta -*< zdir)
      +      , told = tnew
      +      }
      +  where 
      +      delta = anglePerSec world * elapsed / 1000.0
      +      elapsed = fromIntegral (tnew - (told world))
      +
      +shapeFunc :: Scalar -> Function3D
      +shapeFunc res x y = 
      +  let 
      +      z = maxZeroIndex (ymandel x y) 0 1 20
      +  in
      +  if and [ maxZeroIndex (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 |
      +              val <- [res], xeps <- [-val,val], yeps<-[-val,val]]
      +      then Nothing 
      +      else Just (z,colorFromValue 0)
      +
      +colorFromValue :: Point -> Color
      +colorFromValue n =
      +  let 
      +      t :: Point -> Scalar
      +      t i = 0.0 + 0.5*cos( i /10 )
      +  in
      +    makeColor (t n) (t (n+5)) (t (n+10))
      +
      +-- given f min max nbtest,
      +-- considering 
      +--  - f is an increasing function
      +--  - f(min)=0
      +--  - f(max)≠0
      +-- then maxZeroIndex f min max nbtest returns x such that
      +--    f(x - ε)=0 and f(x + ε)≠0
      +--    where ε=(max-min)/2^(nbtest+1) 
      +maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => 
      +                 (a -> b) -> a -> a -> Int -> a
      +maxZeroIndex _ minval maxval 0 = (minval+maxval)/2
      +maxZeroIndex func minval maxval n = 
      +  if func medpoint /= 0 
      +       then maxZeroIndex func minval medpoint (n-1)
      +       else maxZeroIndex func medpoint maxval (n-1)
      +  where medpoint = (minval+maxval)/2
      +
      +ymandel :: Point -> Point -> Point -> Point
      +ymandel x y z = fromIntegral (mandel x y z 64) / 64
      +
      + +
      + +

      And you can also consider minor changes in the YGL.hs source file.

      + +

      Download the source code of this section → 06_Mandelbulb/Mandelbulb.lhs

      +

      Conclusion

      +

      As we can use imperative style in a functional language, know you can use functional style in imperative languages. This article exposed a way to organize some code in a functional way. I’d like to stress the usage of Haskell made it very simple to achieve this.

      +

      Once you are used to pure functional style, it is hard not to see all advantages it offers.

      +

      The code in the two last sections is completely pure and functional. Furthermore I don’t use GLfloat, Color3 or any other OpenGL type. If I want to use another library in the future, I would be able to keep all the pure code and simply update the YGL module.

      +

      The YGL module can be seen as a “wrapper” around 3D display and user interaction. It is a clean separator between the imperative paradigm and functional paradigm.

      +

      If you want to go further, it shouldn’t be hard to add parallelism. This should be easy mainly because most of the visible code is pure. Such an optimization would have been harder by using directly the OpenGL library.

      +

      You should also want to make a more precise object. Because, the Mandelbulb is clearly not convex. But a precise rendering might be very long from O(n².log(n)) to O(n³).

      +
      +
      +
        +
      1. Unfortunately, I couldn’t make this program to work on my Mac. More precisely, I couldn’t make the DevIL library work on Mac to output the image. Yes I have done a brew install libdevil. But even a minimal program who simply write some jpg didn’t worked. I tried both with Haskell and C.

      2. +
      3. Generally in Haskell you need to declare a lot of import lines. This is something I find annoying. In particular, it should be possible to create a special file, Import.hs which make all the necessary import for you, as you generally need them all. I understand why this is cleaner to force the programmer not to do so, but, each time I do a copy/paste, I feel something is wrong. I believe this concern can be generalized to the lack of namespace in Haskell.

      4. +
      5. I tried Complex Double, Complex Float, this current data type with Double and the actual version Float. For rendering a 1024x1024 Mandelbrot set it takes Complex Double about 6.8s, for Complex Float about 5.1s, for the actual version with Double and Float it takes about 1.6 sec. See these sources for testing yourself: https://gist.github.com/2945043. If you really want to things to go faster, use data Complex = C {-# UNPACK #-} !Float {-# UNPACK #-} !Float. It takes only one second instead of 1.6s.

      6. +
      +
      ]]>
      +
      + + Learn Haskell Fast and Hard + + http://yannesposito.com/Scratch/en/blog/Haskell-the-Hard-Way/index.html + 2012-02-08T00:00:00Z + 2012-02-08T00:00:00Z + Magritte pleasure principle

      + +
      + +

      I really believe all developer should learn Haskell. I don’t think all should be super Haskell ninjas, but at least, they should discover what Haskell has to offer. Learning Haskell open your mind.

      +

      Mainstream languages share the same foundations:

      +
        +
      • variables
      • +
      • loops
      • +
      • pointers1
      • +
      • data structures, objects and classes (for most)
      • +
      +

      Haskell is very different. This language uses a lot of concepts I had never heard about before. Many of those concepts will help you become a better programmer.

      +

      But, learning Haskell can be hard. It was for me. In this article I try to provide what I lacked during my learning.

      +

      This article will certainly be hard to follow. This is on purpose. There is no shortcut to learning Haskell. It is hard and challenging. But I believe this is a good thing. It is because it is hard that Haskell is interesting.

      +

      The conventional method to learning Haskell is to read two books. First “Learn You a Haskell” and just after “Real World Haskell”. I also believe this is the right way to go. But, to learn what Haskell is all about, you’ll have to read them in detail.

      +

      On the other hand, this article is a very brief and dense overview of all major aspects of Haskell. I also added some informations I lacked while I learned Haskell.

      +

      The article contains five parts:

      +
        +
      • Introduction: a short example to show Haskell can be friendly.
      • +
      • Basic Haskell: Haskell syntax, and some essential notions.
      • +
      • Hard Difficulty Part: +
          +
        • Functional style; a progressive example, from imperative to functional style
        • +
        • Types; types and a standard binary tree example
        • +
        • Infinite Structure; manipulate an infinite binary tree!
        • +
      • +
      • Hell Difficulty Part: +
          +
        • Deal with IO; A very minimal example
        • +
        • IO trick explained; the hidden detail I lacked to understand IO
        • +
        • Monads; incredible how we can generalize
        • +
      • +
      • Appendix: +
          +
        • More on infinite tree; a more math oriented discussion about infinite trees
        • +
      • +
      +
      +Note: Each time you’ll see a separator with a filename ending in .lhs, you could click the filename to get this file. If you save the file as filename.lhs, you can run it with +
      +runhaskell filename.lhs
      +
      + +

      Some might not work, but most will. You should see a link just below.

      +
      +
      + +
      +

      01_basic/10_Introduction/00_hello_world.lhs

      +

      +Introduction +

      + +

      +Install +

      + +

      + +

      Tools:

      +
        +
      • ghc: Compiler similar to gcc for C.
      • +
      • ghci: Interactive Haskell (REPL)
      • +
      • runhaskell: Execute a program without compiling it. Convenient but very slow compared to compiled programs.
      • +
      +

      +Don’t be afraid +

      + +

      The Scream

      +

      Many book/articles about Haskell start by introducing some esoteric formula (quick sort, Fibonacci, etc…). I will do the exact opposite. At first I won’t show you any Haskell super power. I will start with similarities between Haskell and other programming languages. Let’s jump to the mandatory “Hello World”.

      +
      +
      main = putStrLn "Hello World!"
      +
      +

      To run it, you can save this code in a hello.hs and:

      +
      ~ runhaskell ./hello.hs
      +Hello World!
      +

      You could also download the literate Haskell source. You should see a link just above the introduction title. Download this file as 00_hello_world.lhs and:

      +
      ~ runhaskell 00_hello_world.lhs
      +Hello World!
      +

      01_basic/10_Introduction/00_hello_world.lhs

      +
      +

      01_basic/10_Introduction/10_hello_you.lhs

      +

      Now, a program asking your name and replying “Hello” using the name you entered:

      +
      +
      main = do
      +    print "What is your name?"
      +    name <- getLine
      +    print ("Hello " ++ name ++ "!")
      +
      +

      First, let us compare with a similar program in some imperative languages:

      +
      # Python
      +print "What is your name?"
      +name = raw_input()
      +print "Hello %s!" % name
      +
      # Ruby
      +puts "What is your name?"
      +name = gets.chomp
      +puts "Hello #{name}!"
      +
      // In C
      +#include <stdio.h>
      +int main (int argc, char **argv) {
      +    char name[666]; // <- An Evil Number!
      +    // What if my name is more than 665 character long?
      +    printf("What is your name?\n"); 
      +    scanf("%s", name);
      +    printf("Hello %s!\n", name);
      +    return 0;
      +}
      +

      The structure is the same, but there are some syntax differences. A major part of this tutorial will be dedicated to explaining why.

      +

      In Haskell, there is a main function and every object has a type. The type of main is IO (). This means, main will cause side effects.

      +

      Just remember that Haskell can look a lot like mainstream imperative languages.

      +

      01_basic/10_Introduction/10_hello_you.lhs

      +
      +

      01_basic/10_Introduction/20_very_basic.lhs

      +

      +Very basic Haskell +

      + +

      Picasso minimal owl

      +

      Before continuing you need to be warned about some essential properties of Haskell.

      +

      Functional

      +

      Haskell is a functional language. If you have an imperative language background, you’ll have to learn a lot of new things. Hopefully many of these new concepts will help you to program even in imperative languages.

      +

      Smart Static Typing

      +

      Instead of being in your way like in C, C++ or Java, the type system is here to help you.

      +

      Purity

      +

      Generally your functions won’t modify anything in the outside world. This means, it can’t modify the value of a variable, can’t get user input, can’t write on the screen, can’t launch a missile. On the other hand, parallelism will be very easy to achieve. Haskell makes it clear where effects occur and where you are pure. Also, it will be far easier to reason about your program. Most bugs will be prevented in the pure parts of your program.

      +

      Furthermore pure functions follow a fundamental law in Haskell:

      +
      +

      Applying a function with the same parameters always returns the same value.

      +
      +

      Laziness

      +

      Laziness by default is a very uncommon language design. By default, Haskell evaluates something only when it is needed. In consequence, it provides a very elegant way to manipulate infinite structures for example.

      +

      A last warning on how you should read Haskell code. For me, it is like reading scientific papers. Some parts are very clear, but when you see a formula, just focus and read slower. Also, while learning Haskell, it really doesn’t matter much if you don’t understand syntax details. If you meet a >>=, <$>, <- or any other weird symbol, just ignore them and follows the flow of the code.

      +

      +Function declaration +

      + +

      You might be used to declare functions like this:

      +

      In C:

      +
      int f(int x, int y) {
      +    return x*x + y*y;
      +}
      +

      In Javascript:

      +
      function f(x,y) {
      +    return x*x + y*y;
      +}
      +

      in Python:

      +
      def f(x,y):
      +    return x*x + y*y
      +

      in Ruby:

      +
      def f(x,y)
      +    x*x + y*y
      +end
      +

      In Scheme:

      +
      (define (f x y)
      +    (+ (* x x) (* y y)))
      +

      Finally, the Haskell way is:

      +
      f x y = x*x + y*y
      +

      Very clean. No parenthesis, no def.

      +

      Don’t forget, Haskell uses functions and types a lot. It is thus very easy to define them. The syntax was particularly well thought for these objects.

      +

      +A Type Example +

      + +

      The usual way is to declare the type of your function. This is not mandatory. The compiler is smart enough to discover it for you.

      +

      Let’s play a little.

      +
      +
      -- We declare the type using ::
      +f :: Int -> Int -> Int
      +f x y = x*x + y*y
      +
      +main = print (f 2 3)
      +
      +
      ~ runhaskell 20_very_basic.lhs
      +13
      +

      01_basic/10_Introduction/20_very_basic.lhs

      +
      +

      01_basic/10_Introduction/21_very_basic.lhs

      +

      Now try

      +
      +
      f :: Int -> Int -> Int
      +f x y = x*x + y*y
      +
      +main = print (f 2.3 4.2)
      +
      +

      You get this error:

      +
      21_very_basic.lhs:6:23:
      +    No instance for (Fractional Int)
      +      arising from the literal `4.2'
      +    Possible fix: add an instance declaration for (Fractional Int)
      +    In the second argument of `f', namely `4.2'
      +    In the first argument of `print', namely `(f 2.3 4.2)'
      +    In the expression: print (f 2.3 4.2)
      +

      The problem: 4.2 isn’t an Int.

      +

      01_basic/10_Introduction/21_very_basic.lhs

      +
      +

      01_basic/10_Introduction/22_very_basic.lhs

      +

      The solution, don’t declare the type for f. Haskell will infer the most general type for us:

      +
      +
      f x y = x*x + y*y
      +
      +main = print (f 2.3 4.2)
      +
      +

      It works! Great, we don’t have to declare a new function for every single type. For example, in C, you’ll have to declare a function for int, for float, for long, for double, etc…

      +

      But, what type should we declare? To discover the type Haskell has found for us, just launch ghci:

      +
      
      +% ghci
      +GHCi, version 7.0.4: http://www.haskell.org/ghc/  :? for help
      +Loading package ghc-prim ... linking ... done.
      +Loading package integer-gmp ... linking ... done.
      +Loading package base ... linking ... done.
      +Loading package ffi-1.0 ... linking ... done.
      +Prelude> let f x y = x*x + y*y
      +Prelude> :type f
      +f :: Num a => a -> a -> a
      +
      + +

      Uh? What is this strange type?

      +
      Num a => a -> a -> a
      +

      First, let’s focus on the right part a -> a -> a. To understand it, just look at a list of progressive examples:

      +

      The written type | Its meaning |
      Int | the type Int |
      Int -> Int | the type function from Int to Int |
      Float -> Int | the type function from Float to Int |
      a -> Int | the type function from any type to Int |
      a -> a | the type function from any type a to the same type a |
      a -> a -> a | the type function of two arguments of any type a to the same type a |

      +

      In the type a -> a -> a, the letter a is a type variable. It means f is a function with two arguments and both arguments and the result have the same type. The type variable a could take many different type value. For example Int, Integer, Float

      +

      So instead of having a forced type like in C with declaring the function for int, long, float, double, etc… We declare only one function like in a dynamically typed language.

      +

      Generally a can be any type. For example a String, an Int, but also more complex types, like Trees, other functions, etc… But here our type is prefixed with Num a =>.

      +

      Num is a type class. A type class can be understood as a set of types. Num contains only types which behave like numbers. More precisely, Num is class containing types who implement a specific list of functions, and in particular (+) and (*).

      +

      Type classes are a very powerful language construct. We can do some incredibly powerful stuff with this. More on this later.

      +

      Finally, Num a => a -> a -> a means:

      +

      Let a be a type belonging to the Num type class. This is a function from type a to (a -> a).

      +

      Yes, strange. In fact, in Haskell no function really has two arguments. Instead all functions have only one argument. But we will note that taking two arguments is equivalent to taking one argument and returning a function taking the second argument as parameter.

      +

      More precisely f 3 4 is equivalent to (f 3) 4. Note f 3 is a function:

      +
      f :: Num a :: a -> a -> a
      +
      +g :: Num a :: a -> a
      +g = f 3
      +
      +g y ⇔ 3*3 + y*y
      +

      Another notation exists for functions. The lambda notation allows us to create functions without assigning them a name. We call them anonymous function. We could have written:

      +
      g = \y -> 3*3 + y*y
      +

      The \ is used because it looks like λ and is ASCII.

      +

      If you are not used to functional programming your brain should start to heat up. It is time to make a real application.

      +

      01_basic/10_Introduction/22_very_basic.lhs

      +
      +

      01_basic/10_Introduction/23_very_basic.lhs

      +

      But just before that, we should verify the type system works as expected:

      +
      +
      f :: Num a => a -> a -> a
      +f x y = x*x + y*y
      +
      +main = print (f 3 2.4)
      +
      +

      It works, because, 3 is a valid representation both for Fractional numbers like Float and for Integer. As 2.4 is a Fractional number, 3 is then interpreted as being also a Fractional number.

      +

      01_basic/10_Introduction/23_very_basic.lhs

      +
      +

      01_basic/10_Introduction/24_very_basic.lhs

      +

      If we force our function to work with different types, it will fail:

      +
      +
      f :: Num a => a -> a -> a
      +f x y = x*x + y*y
      +
      +x :: Int
      +x = 3
      +y :: Float
      +y = 2.4
      +main = print (f x y) -- won't work because type x ≠ type y
      +
      +

      The compiler complains. The two parameters must have the same type.

      +

      If you believe it is a bad idea, and the compiler should make the transformation from a type to another for you, you should really watch this great (and funny) video: WAT

      +

      01_basic/10_Introduction/24_very_basic.lhs

      +

      +Essential Haskell +

      + +

      Kandinsky Gugg

      +

      I suggest you to skim this part. Think of it like a reference. Haskell has a lot of features. Many informations are missing here. Get back here if notation feels strange.

      +

      I use the symbol to state that two expression are equivalent. It is a meta notation, does not exists in Haskell. I will also use to show what is the return of an expression.

      +

      +Notations +

      + +
      +Arithmetic +
      + +
      3 + 2 * 6 / 3 ⇔ 3 + ((2*6)/3)
      +
      +Logic +
      + +
      True || False ⇒ True
      +True && False ⇒ False
      +True == False ⇒ False
      +True /= False ⇒ True  (/=) is the operator for different
      +
      +Powers +
      + +
      x^n     for n an integral (understand Int or Integer)
      +x**y    for y any kind of number (Float for example)
      +

      Integer have no limit except the capacity of your machine:

      +
      4^103
      +102844034832575377634685573909834406561420991602098741459288064
      +

      Yeah! And also rational numbers FTW! But you need to import the module Data.Ratio:

      +
      $ ghci
      +....
      +Prelude> :m Data.Ratio
      +Data.Ratio> (11 % 15) * (5 % 3)
      +11 % 9
      +
      +Lists +
      + +
      []                      ⇔ empty list
      +[1,2,3]                 ⇔ List of integral
      +["foo","bar","baz"]     ⇔ List of String
      +1:[2,3]                 ⇔ [1,2,3], (:) prepend one element
      +1:2:[]                  ⇔ [1,2]
      +[1,2] ++ [3,4]          ⇔ [1,2,3,4], (++) concatenate
      +[1,2,3] ++ ["foo"]      ⇔ ERROR String ≠ Integral
      +[1..4]                  ⇔ [1,2,3,4]
      +[1,3..10]               ⇔ [1,3,5,7,9]
      +[2,3,5,7,11..100]       ⇔ ERROR! I am not so smart!
      +[10,9..1]               ⇔ [10,9,8,7,6,5,4,3,2,1]
      +
      +Strings +
      + +

      In Haskell strings are list of Char.

      +
      'a' :: Char
      +"a" :: [Char]
      +""  ⇔ []
      +"ab" ⇔ ['a','b'] ⇔  'a':"b" ⇔ 'a':['b'] ⇔ 'a':'b':[]
      +"abc" ⇔ "ab"++"c"
      +
      +

      Remark: In real code you shouldn’t use list of char to represent text. You should mostly use Data.Text instead. If you want to represent stream of ASCII char, you should use Data.ByteString.

      +
      +
      +Tuples +
      + +

      The type of couple is (a,b). Elements in a tuple can have different type.

      +
      -- All these tuple are valid
      +(2,"foo")
      +(3,'a',[2,3])
      +((2,"a"),"c",3)
      +
      +fst (x,y)       ⇒  x
      +snd (x,y)       ⇒  y
      +
      +fst (x,y,z)     ⇒  ERROR: fst :: (a,b) -> a
      +snd (x,y,z)     ⇒  ERROR: snd :: (a,b) -> b
      +
      +Deal with parentheses +
      + +

      To remove some parentheses you can use two functions: ($) and (.).

      +
      -- By default:
      +f g h x         ⇔  (((f g) h) x)
      +
      +-- the $ replace parenthesis from the $
      +-- to the end of the expression
      +f g $ h x       ⇔  f g (h x) ⇔ (f g) (h x)
      +f $ g h x       ⇔  f (g h x) ⇔ f ((g h) x)
      +f $ g $ h x     ⇔  f (g (h x))
      +
      +-- (.) the composition function
      +(f . g) x       ⇔  f (g x)
      +(f . g . h) x   ⇔  f (g (h x))
      +
      +

      01_basic/20_Essential_Haskell/10a_Functions.lhs

      +

      +Useful notations for functions +

      + +

      Just a reminder:

      +
      x :: Int            ⇔ x is of type Int
      +x :: a              ⇔ x can be of any type
      +x :: Num a => a     ⇔ x can be any type a
      +                      such that a belongs to Num type class 
      +f :: a -> b         ⇔ f is a function from a to b
      +f :: a -> b -> c    ⇔ f is a function from a to (b→c)
      +f :: (a -> b) -> c  ⇔ f is a function from (a→b) to c
      +

      Defining the type of a function before its declaration isn’t mandatory. Haskell infers the most general type for you. But it is considered a good practice to do so.

      +

      Infix notation

      +
      +
      square :: Num a => a -> a  
      +square x = x^2
      +
      +

      Note ^ use infix notation. For each infix operator there its associated prefix notation. You just have to put it inside parenthesis.

      +
      +
      square' x = (^) x 2
      +
      +square'' x = (^2) x
      +
      +

      We can remove x in the left and right side! It’s called η-reduction.

      +
      +
      square''' = (^2)
      +
      +

      Note we can declare function with ' in their name. Here:

      +
      +

      squaresquare'square''square '''

      +
      +

      Tests

      +

      An implementation of the absolute function.

      +
      +
      absolute :: (Ord a, Num a) => a -> a
      +absolute x = if x >= 0 then x else -x
      +
      +

      Note: the if .. then .. else Haskell notation is more like the ¤?¤:¤ C operator. You cannot forget the else.

      +

      Another equivalent version:

      +
      +
      absolute' x
      +    | x >= 0 = x
      +    | otherwise = -x
      +
      + +
      +

      Notation warning: indentation is important in Haskell. Like in Python, a bad indentation could break your code!

      +
      +
      + +
      +
      main = do
      +      print $ square 10
      +      print $ square' 10
      +      print $ square'' 10
      +      print $ square''' 10
      +      print $ absolute 10
      +      print $ absolute (-10)
      +      print $ absolute' 10
      +      print $ absolute' (-10)
      +
      +
      + +

      01_basic/20_Essential_Haskell/10a_Functions.lhs

      +

      +Hard Part +

      + +

      The hard part can now begin.

      +

      +Functional style +

      + +

      Biomechanical Landscape by H.R. Giger

      +

      In this section, I will give a short example of the impressive refactoring ability provided by Haskell. We will select a problem and solve it using a standard imperative way. Then I will make the code evolve. The end result will be both more elegant and easier to adapt.

      +

      Let’s solve the following problem:

      +
      +

      Given a list of integers, return the sum of the even numbers in the list.

      +

      example: [1,2,3,4,5] ⇒ 2 + 4 ⇒ 6

      +
      +

      To show differences between the functional and imperative approach, I’ll start by providing an imperative solution (in Javascript):

      +
      function evenSum(list) {
      +    var result = 0;
      +    for (var i=0; i< list.length ; i++) {
      +        if (list[i] % 2 ==0) {
      +            result += list[i];
      +        }
      +    }
      +    return result;
      +}
      +

      But, in Haskell we don’t have variables, nor for loop. One solution to achieve the same result without loops is to use recursion.

      +
      +

      Remark: Recursion is generally perceived as slow in imperative languages. But it is generally not the case in functional programming. Most of the time Haskell will handle recursive functions efficiently.

      +
      +

      Here is a C version of the recursive function. Note that for simplicity, I assume the int list ends with the first 0 value.

      +
      int evenSum(int *list) {
      +    return accumSum(0,list);
      +}
      +
      +int accumSum(int n, int *list) {
      +    int x;
      +    int *xs;
      +    if (*list == 0) { // if the list is empty
      +        return n;
      +    } else {
      +        x = list[0]; // let x be the first element of the list
      +        xs = list+1; // let xs be the list without x
      +        if ( 0 == (x%2) ) { // if x is even
      +            return accumSum(n+x, xs);
      +        } else {
      +            return accumSum(n, xs);
      +        }
      +    }
      +}
      +

      Keep this code in mind. We will translate it into Haskell. But before, I need to introduce three simple but useful functions we will use:

      +
      even :: Integral a => a -> Bool
      +head :: [a] -> a
      +tail :: [a] -> [a]
      +

      even verifies if a number is even.

      +
      even :: Integral a => a -> Bool
      +even 3   False
      +even 2   True
      +

      head returns the first element of a list:

      +
      head :: [a] -> a
      +head [1,2,3]  1
      +head []       ERROR
      +

      tail returns all elements of a list, except the first:

      +
      tail :: [a] -> [a]
      +tail [1,2,3]  [2,3]
      +tail [3]      []
      +tail []       ERROR
      +

      Note that for any non empty list l, l ⇔ (head l):(tail l)

      +
      +

      02_Hard_Part/11_Functions.lhs

      +

      The first Haskell solution. The function evenSum returns the sum of all even numbers in a list:

      +
      +
      -- Version 1
      +evenSum :: [Integer] -> Integer
      +
      +evenSum l = accumSum 0 l
      +
      +accumSum n l = if l == []
      +                  then n
      +                  else let x = head l 
      +                           xs = tail l 
      +                       in if even x
      +                              then accumSum (n+x) xs
      +                              else accumSum n xs
      +
      +

      To test a function you can use ghci:

      +
      +% ghci
      +GHCi, version 7.0.3: http://www.haskell.org/ghc/  :? for help
      +Loading package ghc-prim ... linking ... done.
      +Loading package integer-gmp ... linking ... done.
      +Loading package base ... linking ... done.
      +Prelude> :load 11_Functions.lhs 
      +[1 of 1] Compiling Main             ( 11_Functions.lhs, interpreted )
      +Ok, modules loaded: Main.
      +*Main> evenSum [1..5]
      +6
      +
      + +

      Here is an example of execution2:

      +
      +*Main> evenSum [1..5]
      +accumSum 0 [1,2,3,4,5]
      +1 is odd
      +accumSum 0 [2,3,4,5]
      +2 is even
      +accumSum (0+2) [3,4,5]
      +3 is odd
      +accumSum (0+2) [4,5]
      +4 is even
      +accumSum (0+2+4) [5]
      +5 is odd
      +accumSum (0+2+4) []
      +l == []
      +0+2+4
      +0+6
      +6
      +
      + +

      Coming from an imperative language all should seem right. In reality many things can be improved. First, we can generalize the type.

      +
      evenSum :: Integral a => [a] -> a
      +
      + +
      +
      main = do print $ evenSum [1..10]
      +
      +
      + +

      02_Hard_Part/11_Functions.lhs

      +
      +

      02_Hard_Part/12_Functions.lhs

      +

      Next, we can use sub functions using where or let. This way our accumSum function won’t pollute the global namespace.

      +
      +
      -- Version 2
      +evenSum :: Integral a => [a] -> a
      +
      +evenSum l = accumSum 0 l
      +    where accumSum n l = 
      +            if l == []
      +                then n
      +                else let x = head l 
      +                         xs = tail l 
      +                     in if even x
      +                            then accumSum (n+x) xs
      +                            else accumSum n xs
      +
      +
      + +
      +
      main = print $ evenSum [1..10]
      +
      +
      + +

      02_Hard_Part/12_Functions.lhs

      +
      +

      02_Hard_Part/13_Functions.lhs

      +

      Next, we can use pattern matching.

      +
      +
      -- Version 3
      +evenSum l = accumSum 0 l
      +    where 
      +        accumSum n [] = n
      +        accumSum n (x:xs) = 
      +             if even x
      +                then accumSum (n+x) xs
      +                else accumSum n xs
      +
      +

      What is pattern matching? Use values instead of general parameter names3.

      +

      Instead of saying: foo l = if l == [] then <x> else <y> You simply state:

      +
      foo [] =  <x>
      +foo l  =  <y>
      +

      But pattern matching goes even further. It is also able to inspect the inner data of a complex value. We can replace

      +
      foo l =  let x  = head l 
      +             xs = tail l
      +         in if even x 
      +             then foo (n+x) xs
      +             else foo n xs
      +

      with

      +
      foo (x:xs) = if even x 
      +                 then foo (n+x) xs
      +                 else foo n xs
      +

      This is a very useful feature. It makes our code both terser and easier to read.

      +
      + +
      +
      main = print $ evenSum [1..10]
      +
      +
      + +

      02_Hard_Part/13_Functions.lhs

      +
      +

      02_Hard_Part/14_Functions.lhs

      +

      In Haskell you can simplify function definition by η-reducing them. For example, instead of writing:

      +
      f x = (some expresion) x
      +

      you can simply write

      +
      f = some expression
      +

      We use this method to remove the l:

      +
      +
      -- Version 4
      +evenSum :: Integral a => [a] -> a
      +
      +evenSum = accumSum 0
      +    where 
      +        accumSum n [] = n
      +        accumSum n (x:xs) = 
      +             if even x
      +                then accumSum (n+x) xs
      +                else accumSum n xs
      +
      +
      + +
      +
      main = print $ evenSum [1..10]
      +
      +
      + +

      02_Hard_Part/14_Functions.lhs

      +
      +

      02_Hard_Part/15_Functions.lhs

      +

      +Higher Order Functions +

      + +

      Escher

      +

      To make things even better we should use higher order functions. What are these beasts? Higher order functions are functions taking functions as parameter.

      +

      Here are some examples:

      +
      filter :: (a -> Bool) -> [a] -> [a]
      +map :: (a -> b) -> [a] -> [b]
      +foldl :: (a -> b -> a) -> a -> [b] -> a
      +

      Let’s proceed by small steps.

      +
      -- Version 5
      +evenSum l = mysum 0 (filter even l)
      +    where
      +      mysum n [] = n
      +      mysum n (x:xs) = mysum (n+x) xs
      +

      where

      +
      filter even [1..10] ⇔  [2,4,6,8,10]
      +

      The function filter takes a function of type (a -> Bool) and a list of type [a]. It returns a list containing only elements for which the function returned true.

      +

      Our next step is to use another way to simulate a loop. We will use the foldl function to accumulate a value. The function foldl captures a general coding pattern:

      +
      +myfunc list = foo initialValue list
      +    foo accumulated []     = accumulated
      +    foo tmpValue    (x:xs) = foo (bar tmpValue x) xs
      +
      + +

      Which can be replaced by:

      +
      +myfunc list = foldl bar initialValue list
      +
      + +

      If you really want to know how the magic works. Here is the definition of foldl.

      +
      foldl f z [] = z
      +foldl f z (x:xs) = foldl f (f z x) xs
      +
      foldl f z [x1,...xn]
      +⇔  f (... (f (f z x1) x2) ...) xn
      +

      But as Haskell is lazy, it doesn’t evaluate (f z x) and pushes it to the stack. This is why we generally use foldl' instead of foldl; foldl' is a strict version of foldl. If you don’t understand what lazy and strict means, don’t worry, just follow the code as if foldl and foldl' where identical.

      +

      Now our new version of evenSum becomes:

      +
      -- Version 6
      +-- foldl' isn't accessible by default
      +-- we need to import it from the module Data.List
      +import Data.List
      +evenSum l = foldl' mysum 0 (filter even l)
      +  where mysum acc value = acc + value
      +

      Version we can simplify by using directly a lambda notation. This way we don’t have to create the temporary name mysum.

      +
      +
      -- Version 7
      +-- Generally it is considered a good practice
      +-- to import only the necessary function(s)
      +import Data.List (foldl')
      +evenSum l = foldl' (\x y -> x+y) 0 (filter even l)
      +
      +

      And of course, we note that

      +
      (\x y -> x+y) ⇔ (+)
      +
      + +
      +
      main = print $ evenSum [1..10]
      +
      +
      + +

      02_Hard_Part/15_Functions.lhs

      +
      +

      02_Hard_Part/16_Functions.lhs

      +

      Finally

      +
      -- Version 8
      +import Data.List (foldl')
      +evenSum :: Integral a => [a] -> a
      +evenSum l = foldl' (+) 0 (filter even l)
      +

      foldl' isn’t the easiest function to intuit. If you are not used to it, you should study it a bit.

      +

      To help you understand what’s going on here, a step by step evaluation:

      +
      +  evenSum [1,2,3,4]
      +⇒ foldl' (+) 0 (filter even [1,2,3,4])
      +⇒ foldl' (+) 0 [2,4]
      +⇒ foldl' (+) (0+2) [4] 
      +⇒ foldl' (+) 2 [4]
      +⇒ foldl' (+) (2+4) []
      +⇒ foldl' (+) 6 []
      +⇒ 6
      +
      + +

      Another useful higher order function is (.). The (.) function corresponds to the mathematical composition.

      +
      (f . g . h) x ⇔  f ( g (h x))
      +

      We can take advantage of this operator to η-reduce our function:

      +
      -- Version 9
      +import Data.List (foldl')
      +evenSum :: Integral a => [a] -> a
      +evenSum = (foldl' (+) 0) . (filter even)
      +

      Also, we could rename some parts to make it clearer:

      +
      +
      -- Version 10 
      +import Data.List (foldl')
      +sum' :: (Num a) => [a] -> a
      +sum' = foldl' (+) 0
      +evenSum :: Integral a => [a] -> a
      +evenSum = sum' . (filter even)
      +
      +

      It is time to discuss a bit. What did we gain by using higher order functions?

      +

      At first, you can say it is terseness. But in fact, it has more to do with better thinking. Suppose we want to modify slightly our function. We want to get the sum of all even square of element of the list.

      +
      [1,2,3,4] ▷ [1,4,9,16] ▷ [4,16] ▷ 20
      +

      Update the version 10 is extremely easy:

      +
      +
      squareEvenSum = sum' . (filter even) . (map (^2))
      +squareEvenSum' = evenSum . (map (^2))
      +squareEvenSum'' = sum' . (map (^2)) . (filter even)
      +
      +

      We just had to add another “transformation function”4.

      +
      map (^2) [1,2,3,4] ⇔ [1,4,9,16]
      +

      The map function simply apply a function to all element of a list.

      +

      We didn’t had to modify anything inside the function definition. It feels more modular. But in addition you can think more mathematically about your function. You can then use your function as any other one. You can compose, map, fold, filter using your new function.

      +

      To modify version 1 is left as an exercise to the reader ☺.

      +

      If you believe we reached the end of generalization, then know you are very wrong. For example, there is a way to not only use this function on lists but on any recursive type. If you want to know how, I suggest you to read this quite fun article: Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire by Meijer, Fokkinga and Paterson.

      +

      This example should show you how great pure functional programming is. Unfortunately, using pure functional programming isn’t well suited to all usages. Or at least such a language hasn’t been found yet.

      +

      One of the great powers of Haskell is the ability to create DSLs (Domain Specific Language) making it easy to change the programming paradigm.

      +

      In fact, Haskell is also great when you want to write imperative style programming. Understanding this was really hard for me when learning Haskell. A lot of effort has been done to explain to you how much functional approach is superior. Then when you start the imperative style of Haskell, it is hard to understand why and how.

      +

      But before talking about this Haskell super-power, we must talk about another essential aspect of Haskell: Types.

      +
      + +
      +
      main = print $ evenSum [1..10]
      +
      +
      + +

      02_Hard_Part/16_Functions.lhs

      +

      +Types +

      + +

      Dali, the madonna of port Lligat

      +
      +

      tl;dr:

      +
        +
      • type Name = AnotherType is just an alias and the compiler doesn’t do any difference between Name and AnotherType.
      • +
      • data Name = NameConstructor AnotherType make a difference.
      • +
      • data can construct structures which can be recursives.
      • +
      • deriving is magic and create functions for you.
      • +
      +
      +

      In Haskell, types are strong and static.

      +

      Why is this important? It will help you greatly to avoid mistakes. In Haskell, most bugs are caught during the compilation of your program. And the main reason is because of the type inference during compilation. It will be easy to detect where you used the wrong parameter at the wrong place for example.

      +

      +Type inference +

      + +

      Static typing is generally essential to reach fast execution time. But most statically typed languages are bad at generalizing concepts. Haskell’s saving grace is that it can infer types.

      +

      Here is a simple example. The square function in Haskell:

      +
      square x = x * x
      +

      This function can square any Numeral type. You can provide square with an Int, an Integer, a Float a Fractional and even Complex. Proof by example:

      +
      % ghci
      +GHCi, version 7.0.4:
      +...
      +Prelude> let square x = x*x
      +Prelude> square 2
      +4
      +Prelude> square 2.1
      +4.41
      +Prelude> -- load the Data.Complex module
      +Prelude> :m Data.Complex
      +Prelude Data.Complex> square (2 :+ 1)
      +3.0 :+ 4.0
      +

      x :+ y is the notation for the complex (x + ib).

      +

      Now compare with the amount of code necessary in C:

      +
      int     int_square(int x) { return x*x; }
      +
      +float   float_square(float x) {return x*x; }
      +
      +complex complex_square (complex z) {
      +    complex tmp;
      +    tmp.real = z.real * z.real - z.img * z.img;
      +    tmp.img = 2 * z.img * z.real;
      +}
      +
      +complex x,y;
      +y = complex_square(x);
      +

      For each type, you need to write a new function. The only way to work around this problem is to use some meta-programming trick. For example using the pre-processor. In C++ there is a better way, the C++ templates:

      +

      ~~~~~~ {.c++} #include #include using namespace std;

      +

      template T square(T x) { return x*x; }

      +

      int main() { // int int sqr_of_five = square(5); cout << sqr_of_five << endl; // double cout << (double)square(5.3) << endl; // complex cout << square( complex(5,3) ) << endl; return 0; } ~~~~~~

      +

      C++ does a far better job than C. For more complex function the syntax can be hard to follow: look at this article for example.

      +

      In C++ you must declare that a function can work with different types. In Haskell this is the opposite. The function will be as general as possible by default.

      +

      Type inference gives Haskell the feeling of freedom that dynamically typed languages provide. But unlike dynamically typed languages, most errors are caught before the execution. Generally, in Haskell:

      +
      +

      “if it compiles it certainly does what you intended”

      +
      +
      +

      02_Hard_Part/21_Types.lhs

      +

      +Type construction +

      + +

      You can construct your own types. First you can use aliases or type synonyms.

      +
      +
      type Name   = String
      +type Color  = String
      +
      +showInfos :: Name ->  Color -> String
      +showInfos name color =  "Name: " ++ name
      +                        ++ ", Color: " ++ color
      +name :: Name
      +name = "Robin"
      +color :: Color
      +color = "Blue"
      +main = putStrLn $ showInfos name color
      +
      +

      02_Hard_Part/21_Types.lhs

      +
      +

      02_Hard_Part/22_Types.lhs

      +

      But it doesn’t protect you much. Try to swap the two parameter of showInfos and run the program:

      +
          putStrLn $ showInfos color name
      +

      It will compile and execute. In fact you can replace Name, Color and String everywhere. The compiler will treat them as completely identical.

      +

      Another method is to create your own types using the keyword data.

      +
      +
      data Name   = NameConstr String
      +data Color  = ColorConstr String
      +
      +showInfos :: Name ->  Color -> String
      +showInfos (NameConstr name) (ColorConstr color) =
      +      "Name: " ++ name ++ ", Color: " ++ color
      +
      +name  = NameConstr "Robin"
      +color = ColorConstr "Blue"
      +main = putStrLn $ showInfos name color
      +
      +

      Now if you switch parameters of showInfos, the compiler complains! A possible mistake you could never do again. The only price is to be more verbose.

      +

      Also remark constructor are functions:

      +
      NameConstr  :: String -> Name
      +ColorConstr :: String -> Color
      +

      The syntax of data is mainly:

      +
      data TypeName =   ConstructorName  [types]
      +                | ConstructorName2 [types]
      +                | ...
      +

      Generally the usage is to use the same name for the DataTypeName and DataTypeConstructor.

      +

      Example:

      +
      data Complex = Num a => Complex a a
      +

      Also you can use the record syntax:

      +
      data DataTypeName = DataConstructor {
      +                      field1 :: [type of field1]
      +                    , field2 :: [type of field2]
      +                    ...
      +                    , fieldn :: [type of fieldn] }
      +

      And many accessors are made for you. Furthermore you can use another order when setting values.

      +

      Example:

      +
      data Complex = Num a => Complex { real :: a, img :: a}
      +c = Complex 1.0 2.0
      +z = Complex { real = 3, img = 4 }
      +real c  1.0
      +img z  4
      +

      02_Hard_Part/22_Types.lhs

      +
      +

      02_Hard_Part/23_Types.lhs

      +

      +Recursive type +

      + +

      You already encountered a recursive type: lists. You can re-create lists, but with a more verbose syntax:

      +
      data List a = Empty | Cons a (List a)
      +

      If you really want to use an easier syntax you can use an infix name for constructors.

      +
      infixr 5 :::
      +data List a = Nil | a ::: (List a)
      +

      The number after infixr is the priority.

      +

      If you want to be able to print (Show), read (Read), test equality (Eq) and compare (Ord) your new data structure you can tell Haskell to derive the appropriate functions for you.

      +
      +
      infixr 5 :::
      +data List a = Nil | a ::: (List a) 
      +              deriving (Show,Read,Eq,Ord)
      +
      +

      When you add deriving (Show) to your data declaration, Haskell create a show function for you. We’ll see soon how you can use your own show function.

      +
      +
      convertList [] = Nil
      +convertList (x:xs) = x ::: convertList xs
      +
      +
      +
      main = do
      +      print (0 ::: 1 ::: Nil)
      +      print (convertList [0,1])
      +
      +

      This prints:

      +
      0 ::: (1 ::: Nil)
      +0 ::: (1 ::: Nil)
      +

      02_Hard_Part/23_Types.lhs

      +
      +

      02_Hard_Part/30_Trees.lhs

      +

      +Trees +

      + +

      Magritte, l

      +

      We’ll just give another standard example: binary trees.

      +
      +
      import Data.List
      +
      +data BinTree a = Empty
      +                 | Node a (BinTree a) (BinTree a)
      +                              deriving (Show)
      +
      +

      We will also create a function which turns a list into an ordered binary tree.

      +
      +
      treeFromList :: (Ord a) => [a] -> BinTree a
      +treeFromList [] = Empty
      +treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
      +                             (treeFromList (filter (>x) xs))
      +
      +

      Look at how elegant this function is. In plain English:

      +
        +
      • an empty list will be converted to an empty tree.
      • +
      • a list (x:xs) will be converted to a tree where:
      • +
      • The root is x
      • +
      • Its left subtree is the tree created from members of the list xs which are strictly inferior to x and
      • +
      • the right subtree is the tree created from members of the list xs which are strictly superior to x.
      • +
      +
      +
      main = print $ treeFromList [7,2,4,8]
      +
      +

      You should obtain the following:

      +
      Node 7 (Node 2 Empty (Node 4 Empty Empty)) (Node 8 Empty Empty)
      +

      This is an informative but quite unpleasant representation of our tree.

      +

      02_Hard_Part/30_Trees.lhs

      +
      +

      02_Hard_Part/31_Trees.lhs

      +

      Just for fun, let’s code a better display for our trees. I simply had fun making a nice function to display trees in a general way. You can safely skip this part if you find it too difficult to follow.

      +

      We have a few changes to make. We remove the deriving (Show) from the declaration of our BinTree type. And it might also be useful to make our BinTree an instance of (Eq and Ord). We will be able to test equality and compare trees.

      +
      +
      data BinTree a = Empty
      +                 | Node a (BinTree a) (BinTree a)
      +                  deriving (Eq,Ord)
      +
      +

      Without the deriving (Show), Haskell doesn’t create a show method for us. We will create our own version of show. To achieve this, we must declare that our newly created type BinTree a is an instance of the type class Show. The general syntax is:

      +
      instance Show (BinTree a) where
      +   show t = ... -- You declare your function here
      +

      Here is my version of how to show a binary tree. Don’t worry about the apparent complexity. I made a lot of improvements in order to display even stranger objects.

      +
      +
      -- declare BinTree a to be an instance of Show
      +instance (Show a) => Show (BinTree a) where
      +  -- will start by a '<' before the root
      +  -- and put a : a begining of line
      +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
      +    where
      +    -- treeshow pref Tree
      +    --   shows a tree and starts each line with pref
      +    -- We don't display the Empty tree
      +    treeshow pref Empty = ""
      +    -- Leaf
      +    treeshow pref (Node x Empty Empty) =
      +                  (pshow pref x)
      +
      +    -- Right branch is empty
      +    treeshow pref (Node x left Empty) =
      +                  (pshow pref x) ++ "\n" ++
      +                  (showSon pref "`--" "   " left)
      +
      +    -- Left branch is empty
      +    treeshow pref (Node x Empty right) =
      +                  (pshow pref x) ++ "\n" ++
      +                  (showSon pref "`--" "   " right)
      +
      +    -- Tree with left and right children non empty
      +    treeshow pref (Node x left right) =
      +                  (pshow pref x) ++ "\n" ++
      +                  (showSon pref "|--" "|  " left) ++ "\n" ++
      +                  (showSon pref "`--" "   " right)
      +
      +    -- shows a tree using some prefixes to make it nice
      +    showSon pref before next t =
      +                  pref ++ before ++ treeshow (pref ++ next) t
      +
      +    -- pshow replaces "\n" by "\n"++pref
      +    pshow pref x = replace '\n' ("\n"++pref) (show x)
      +
      +    -- replaces one char by another string
      +    replace c new string =
      +      concatMap (change c new) string
      +      where
      +          change c new x
      +              | x == c = new
      +              | otherwise = x:[] -- "x"
      +
      +

      The treeFromList method remains identical.

      +
      +
      treeFromList :: (Ord a) => [a] -> BinTree a
      +treeFromList [] = Empty
      +treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
      +                             (treeFromList (filter (>x) xs))
      +
      +

      And now, we can play:

      +
      +
      main = do
      +  putStrLn "Int binary tree:"
      +  print $ treeFromList [7,2,4,8,1,3,6,21,12,23]
      +
      +
      Int binary tree:
      +< 7
      +: |--2
      +: |  |--1
      +: |  `--4
      +: |     |--3
      +: |     `--6
      +: `--8
      +:    `--21
      +:       |--12
      +:       `--23
      +

      Now it is far better! The root is shown by starting the line with the < character. And each following line starts with a :. But we could also use another type.

      +
      +
        putStrLn "\nString binary tree:"
      +  print $ treeFromList ["foo","bar","baz","gor","yog"]
      +
      +
      String binary tree:
      +< "foo"
      +: |--"bar"
      +: |  `--"baz"
      +: `--"gor"
      +:    `--"yog"
      +

      As we can test equality and order trees, we can make tree of trees!

      +
      +
        putStrLn "\nBinary tree of Char binary trees:"
      +  print ( treeFromList
      +           (map treeFromList ["baz","zara","bar"]))
      +
      +
      Binary tree of Char binary trees:
      +< < 'b'
      +: : |--'a'
      +: : `--'z'
      +: |--< 'b'
      +: |  : |--'a'
      +: |  : `--'r'
      +: `--< 'z'
      +:    : `--'a'
      +:    :    `--'r'
      +

      This is why I chose to prefix each line of tree display by : (except for the root).

      +

      Yo Dawg Tree

      +
      +
        putStrLn "\nTree of Binary trees of Char binary trees:"
      +  print $ (treeFromList . map (treeFromList . map treeFromList))
      +             [ ["YO","DAWG"]
      +             , ["I","HEARD"]
      +             , ["I","HEARD"]
      +             , ["YOU","LIKE","TREES"] ]
      +
      +

      Which is equivalent to

      +
      print ( treeFromList (
      +          map treeFromList
      +             [ map treeFromList ["YO","DAWG"]
      +             , map treeFromList ["I","HEARD"]
      +             , map treeFromList ["I","HEARD"]
      +             , map treeFromList ["YOU","LIKE","TREES"] ]))
      +

      and gives:

      +
      Binary tree of Binary trees of Char binary trees:
      +< < < 'Y'
      +: : : `--'O'
      +: : `--< 'D'
      +: :    : |--'A'
      +: :    : `--'W'
      +: :    :    `--'G'
      +: |--< < 'I'
      +: |  : `--< 'H'
      +: |  :    : |--'E'
      +: |  :    : |  `--'A'
      +: |  :    : |     `--'D'
      +: |  :    : `--'R'
      +: `--< < 'Y'
      +:    : : `--'O'
      +:    : :    `--'U'
      +:    : `--< 'L'
      +:    :    : `--'I'
      +:    :    :    |--'E'
      +:    :    :    `--'K'
      +:    :    `--< 'T'
      +:    :       : `--'R'
      +:    :       :    |--'E'
      +:    :       :    `--'S'
      +

      Notice how duplicate trees aren’t inserted; there is only one tree corresponding to "I","HEARD". We have this for (almost) free, because we have declared Tree to be an instance of Eq.

      +

      See how awesome this structure is. We can make trees containing not only integers, strings and chars, but also other trees. And we can even make a tree containing a tree of trees!

      +

      02_Hard_Part/31_Trees.lhs

      +
      +

      02_Hard_Part/40_Infinites_Structures.lhs

      +

      +Infinite Structures +

      + +

      Escher

      +

      It is often stated that Haskell is lazy.

      +

      In fact, if you are a bit pedantic, you should state that Haskell is non-strict. Laziness is just a common implementation for non-strict languages.

      +

      Then what does not-strict means? From the Haskell wiki:

      +
      +

      Reduction (the mathematical term for evaluation) proceeds from the outside in.

      +

      so if you have (a+(b*c)) then you first reduce + first, then you reduce the inner (b*c)

      +
      +

      For example in Haskell you can do:

      +
      +
      -- numbers = [1,2,..]
      +numbers :: [Integer]
      +numbers = 0:map (1+) numbers
      +
      +take' n [] = []
      +take' 0 l = []
      +take' n (x:xs) = x:take' (n-1) xs
      +
      +main = print $ take' 10 numbers
      +
      +

      And it stops.

      +

      How?

      +

      Instead of trying to evaluate numbers entirely, it evaluates elements only when needed.

      +

      Also, note in Haskell there is a notation for infinite lists

      +
      [1..]   ⇔ [1,2,3,4...]
      +[1,3..] ⇔ [1,3,5,7,9,11...]
      +

      And most functions will work with them. Also, there is a built-in function take which is equivalent to our take'.

      +

      02_Hard_Part/40_Infinites_Structures.lhs

      +
      +

      02_Hard_Part/41_Infinites_Structures.lhs

      +
      + +

      This code is mostly the same as the previous one.

      +
      +
      import Debug.Trace (trace)
      +import Data.List
      +data BinTree a = Empty 
      +                 | Node a (BinTree a) (BinTree a) 
      +                  deriving (Eq,Ord)
      +
      +
      +
      -- declare BinTree a to be an instance of Show
      +instance (Show a) => Show (BinTree a) where
      +  -- will start by a '<' before the root
      +  -- and put a : a begining of line
      +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
      +    where
      +    treeshow pref Empty = ""
      +    treeshow pref (Node x Empty Empty) = 
      +                  (pshow pref x)
      +
      +    treeshow pref (Node x left Empty) = 
      +                  (pshow pref x) ++ "\n" ++
      +                  (showSon pref "`--" "   " left)
      +
      +    treeshow pref (Node x Empty right) = 
      +                  (pshow pref x) ++ "\n" ++
      +                  (showSon pref "`--" "   " right)
      +
      +    treeshow pref (Node x left right) = 
      +                  (pshow pref x) ++ "\n" ++
      +                  (showSon pref "|--" "|  " left) ++ "\n" ++
      +                  (showSon pref "`--" "   " right)
      +
      +    -- show a tree using some prefixes to make it nice
      +    showSon pref before next t = 
      +                  pref ++ before ++ treeshow (pref ++ next) t
      +
      +    -- pshow replace "\n" by "\n"++pref
      +    pshow pref x = replace '\n' ("\n"++pref) (" " ++ show x)
      +
      +    -- replace on char by another string
      +    replace c new string =
      +      concatMap (change c new) string
      +      where
      +          change c new x 
      +              | x == c = new
      +              | otherwise = x:[] -- "x"
      +
      +
      + +

      Suppose we don’t mind having an ordered binary tree. Here is an infinite binary tree:

      +
      +
      nullTree = Node 0 nullTree nullTree
      +
      +

      A complete binary tree where each node is equal to 0. Now I will prove you can manipulate this object using the following function:

      +
      +
      -- take all element of a BinTree 
      +-- up to some depth
      +treeTakeDepth _ Empty = Empty
      +treeTakeDepth 0 _     = Empty
      +treeTakeDepth n (Node x left right) = let
      +          nl = treeTakeDepth (n-1) left
      +          nr = treeTakeDepth (n-1) right
      +          in
      +              Node x nl nr
      +
      +

      See what occurs for this program:

      +
      main = print $ treeTakeDepth 4 nullTree
      +

      This code compiles, runs and stops giving the following result:

      +
      <  0
      +: |-- 0
      +: |  |-- 0
      +: |  |  |-- 0
      +: |  |  `-- 0
      +: |  `-- 0
      +: |     |-- 0
      +: |     `-- 0
      +: `-- 0
      +:    |-- 0
      +:    |  |-- 0
      +:    |  `-- 0
      +:    `-- 0
      +:       |-- 0
      +:       `-- 0
      +

      Just to heat up your neurones a bit more, let’s make a slightly more interesting tree:

      +
      +
      iTree = Node 0 (dec iTree) (inc iTree)
      +        where
      +           dec (Node x l r) = Node (x-1) (dec l) (dec r) 
      +           inc (Node x l r) = Node (x+1) (inc l) (inc r) 
      +
      +

      Another way to create this tree is to use a higher order function. This function should be similar to map, but should work on BinTree instead of list. Here is such a function:

      +
      +
      -- apply a function to each node of Tree
      +treeMap :: (a -> b) -> BinTree a -> BinTree b
      +treeMap f Empty = Empty
      +treeMap f (Node x left right) = Node (f x) 
      +                                     (treeMap f left) 
      +                                     (treeMap f right)
      +
      +

      Hint: I won’t talk more about this here. If you are interested by the generalization of map to other data structures, search for functor and fmap.

      +

      Our definition is now:

      +
      +
      infTreeTwo :: BinTree Int
      +infTreeTwo = Node 0 (treeMap (\x -> x-1) infTreeTwo) 
      +                    (treeMap (\x -> x+1) infTreeTwo) 
      +
      +

      Look at the result for

      +
      main = print $ treeTakeDepth 4 infTreeTwo
      +
      <  0
      +: |-- -1
      +: |  |-- -2
      +: |  |  |-- -3
      +: |  |  `-- -1
      +: |  `-- 0
      +: |     |-- -1
      +: |     `-- 1
      +: `-- 1
      +:    |-- 0
      +:    |  |-- -1
      +:    |  `-- 1
      +:    `-- 2
      +:       |-- 1
      +:       `-- 3
      +
      + +
      +
      main = do
      +  print $ treeTakeDepth 4 nullTree
      +  print $ treeTakeDepth 4 infTreeTwo
      +
      +
      + +

      02_Hard_Part/41_Infinites_Structures.lhs

      +

      +Hell Difficulty Part +

      + +

      Congratulations for getting so far! Now, some of the really hardcore stuff can start.

      +

      If you are like me, you should get the functional style. You should also understand a bit more the advantages of laziness by default. But you also don’t really understand where to start in order to make a real program. And in particular:

      +
        +
      • How do you deal with effects?
      • +
      • Why is there a strange imperative-like notation for dealing with IO?
      • +
      +

      Be prepared, the answers might be complex. But they all be very rewarding.

      +
      +

      03_Hell/01_IO/01_progressive_io_example.lhs

      +

      +Deal With IO +

      + +

      Magritte, Carte blanche

      +
      +

      tl;dr:

      +

      A typical function doing IO looks a lot like an imperative program:

      +
      f :: IO a
      +f = do
      +  x <- action1
      +  action2 x
      +  y <- action3
      +  action4 x y
      +
        +
      • To set a value to an object we use <- .
      • +
      • The type of each line is IO *; in this example:
      • +
      • action1 :: IO b
      • +
      • action2 x :: IO ()
      • +
      • action3 :: IO c
      • +
      • action4 x y :: IO a
      • +
      • x :: b, y :: c
      • +
      • Few objects have the type IO a, this should help you choose. In particular you cannot use pure functions directly here. To use pure functions you could do action2 (purefunction x) for example.
      • +
      +
      +

      In this section, I will explain how to use IO, not how it works. You’ll see how Haskell separates the pure from the impure parts of the program.

      +

      Don’t stop because you’re trying to understand the details of the syntax. Answers will come in the next section.

      +

      What to achieve?

      +
      +

      Ask a user to enter a list of numbers. Print the sum of the numbers

      +
      +
      +
      toList :: String -> [Integer]
      +toList input = read ("[" ++ input ++ "]")
      +
      +main = do
      +  putStrLn "Enter a list of numbers (separated by comma):"
      +  input <- getLine
      +  print $ sum (toList input)
      +
      +

      It should be straightforward to understand the behavior of this program. Let’s analyze the types in more detail.

      +
      putStrLn :: String -> IO ()
      +getLine  :: IO String
      +print    :: Show a => a -> IO ()
      +

      Or more interestingly, we note that each expression in the do block has a type of IO a.

      +
      +main = do
      +  putStrLn "Enter ... " :: IO ()
      +  getLine               :: IO String
      +  print Something       :: IO ()
      +
      + +

      We should also pay attention to the effect of the <- symbol.

      +
      do
      + x <- something
      +

      If something :: IO a then x :: a.

      +

      Another important note about using IO. All lines in a do block must be of one of the two forms:

      +
      action1             :: IO a
      +                    -- in this case, generally a = ()
      +

      or

      +
      value <- action2    -- where
      +                    -- bar z t :: IO b
      +                    -- value   :: b
      +

      These two kinds of line will correspond to two different ways of sequencing actions. The meaning of this sentence should be clearer by the end of the next section.

      +

      03_Hell/01_IO/01_progressive_io_example.lhs

      +
      +

      03_Hell/01_IO/02_progressive_io_example.lhs

      +

      Now let’s see how this program behaves. For example, what occur if the user enter something strange? Let’s try:

      +
          % runghc 02_progressive_io_example.lhs
      +    Enter a list of numbers (separated by comma):
      +    foo
      +    Prelude.read: no parse
      +

      Argh! An evil error message and a crash! The first evolution will be to answer with a more friendly message.

      +

      In order to do this, we must detect that something went wrong. Here is one way to do this. Use the type Maybe. It is a very common type in Haskell.

      +
      +
      import Data.Maybe
      +
      +

      What is this thing? Maybe is a type which takes one parameter. Its definition is:

      +
      data Maybe a = Nothing | Just a
      +

      This is a nice way to tell there was an error while trying to create/compute a value. The maybeRead function is a great example of this. This is a function similar to the function read5, but if something goes wrong the returned value is Nothing. If the value is right, it returns Just <the value>. Don’t try to understand too much of this function. I use a lower level function than read; reads.

      +
      +
      maybeRead :: Read a => String -> Maybe a
      +maybeRead s = case reads s of
      +                  [(x,"")]    -> Just x
      +                  _           -> Nothing
      +
      +

      Now to be a bit more readable, we define a function which goes like this: If the string has the wrong format, it will return Nothing. Otherwise, for example for “1,2,3”, it will return Just [1,2,3].

      +
      +
      getListFromString :: String -> Maybe [Integer]
      +getListFromString str = maybeRead $ "[" ++ str ++ "]"
      +
      +

      We simply have to test the value in our main function.

      +
      +
      main :: IO ()
      +main = do
      +  putStrLn "Enter a list of numbers (separated by comma):"
      +  input <- getLine
      +  let maybeList = getListFromString input in
      +      case maybeList of
      +          Just l  -> print (sum l)
      +          Nothing -> error "Bad format. Good Bye."
      +
      +

      In case of error, we display a nice error message.

      +

      Note that the type of each expression in the main’s do block remains of the form IO a. The only strange construction is error. I’ll say error msg will simply take the needed type (here IO ()).

      +

      One very important thing to note is the type of all the functions defined so far. There is only one function which contains IO in its type: main. This means main is impure. But main uses getListFromString which is pure. It is then clear just by looking at declared types which functions are pure and which are impure.

      +

      Why does purity matter? I certainly forget many advantages, but the three main reasons are:

      +
        +
      • It is far easier to think about pure code than impure one.
      • +
      • Purity protects you from all the hard to reproduce bugs due to side effects.
      • +
      • You can evaluate pure functions in any order or in parallel without risk.
      • +
      +

      This is why you should generally put as most code as possible inside pure functions.

      +

      03_Hell/01_IO/02_progressive_io_example.lhs

      +
      +

      03_Hell/01_IO/03_progressive_io_example.lhs

      +

      Our next evolution will be to prompt the user again and again until she enters a valid answer.

      +

      We keep the first part:

      +
      +
      import Data.Maybe
      +
      +maybeRead :: Read a => String -> Maybe a
      +maybeRead s = case reads s of
      +                  [(x,"")]    -> Just x
      +                  _           -> Nothing
      +getListFromString :: String -> Maybe [Integer]
      +getListFromString str = maybeRead $ "[" ++ str ++ "]"
      +
      +

      Now, we create a function which will ask the user for an list of integers until the input is right.

      +
      +
      askUser :: IO [Integer]
      +askUser = do
      +  putStrLn "Enter a list of numbers (separated by comma):"
      +  input <- getLine
      +  let maybeList = getListFromString input in
      +      case maybeList of
      +          Just l  -> return l
      +          Nothing -> askUser
      +
      +

      This function is of type IO [Integer]. Such a type means that we retrieved a value of type [Integer] through some IO actions. Some people might explain while waving their hands:

      +
      +

      «This is an [Integer] inside an IO»

      +
      +

      If you want to understand the details behind all of this, you’ll have to read the next section. But sincerely, if you just want to use IO. Just practice a little and remember to think about the type.

      +

      Finally our main function is quite simpler:

      +
      +
      main :: IO ()
      +main = do
      +  list <- askUser
      +  print $ sum list
      +
      +

      We have finished with our introduction to IO. This was quite fast. Here are the main things to remember:

      +
        +
      • in the do bloc, each expression must have the type IO a. You are then limited in the number of expressions available. For example, getLine, print, putStrLn, etc…
      • +
      • Try to externalize the pure functions as much as possible.
      • +
      • the IO a type means: an IO action which returns an element of type a. IO represents actions; under the hood, IO a is the type of a function. Read the next section if you are curious.
      • +
      +

      If you practice a bit, you should be able to use IO.

      +
      +

      Exercises:

      +
        +
      • Make a program that sums all of its arguments. Hint: use the function getArgs.
      • +
      +
      +

      03_Hell/01_IO/03_progressive_io_example.lhs

      +

      +IO trick explained +

      + +

      Magritte, ceci n

      +
      +

      Here is a tl;dr: for this section.

      +

      To separate pure and impure parts, main is defined as a function which modifies the state of the world

      +
      main :: World -> World
      +

      A function is guaranteed to have side effects only if it has this type. But look at a typical main function:

      +
      main w0 =
      +    let (v1,w1) = action1 w0 in
      +    let (v2,w2) = action2 v1 w1 in
      +    let (v3,w3) = action3 v2 w2 in
      +    action4 v3 w3
      +

      We have a lot of temporary elements (here w1, w2 and w3) which must be passed on to the next action.

      +

      We create a function bind or (>>=). With bind we don’t need temporary names anymore.

      +
      main =
      +  action1 >>= action2 >>= action3 >>= action4
      +

      Bonus: Haskell has syntactical sugar for us:

      +
      main = do
      +  v1 <- action1
      +  v2 <- action2 v1
      +  v3 <- action3 v2
      +  action4 v3
      +
      +

      Why did we use this strange syntax, and what exactly is this IO type? It looks a bit like magic.

      +

      For now let’s just forget all about the pure parts of our program, and focus on the impure parts:

      +
      askUser :: IO [Integer]
      +askUser = do
      +  putStrLn "Enter a list of numbers (separated by commas):"
      +  input <- getLine
      +  let maybeList = getListFromString input in
      +      case maybeList of
      +          Just l  -> return l
      +          Nothing -> askUser
      +
      +main :: IO ()
      +main = do
      +  list <- askUser
      +  print $ sum list
      +

      First remark; it looks like an imperative structure. Haskell is powerful enough to make impure code look imperative. For example, if you wish you could create a while in Haskell. In fact, for dealing with IO, imperative style is generally more appropriate.

      +

      But you should had noticed the notation is a bit unusual. Here is why, in detail.

      +

      In an impure language, the state of the world can be seen as a huge hidden global variable. This hidden variable is accessible by all functions of your language. For example, you can read and write a file in any function. The fact that a file exists or not can be seen as different states of the world.

      +

      For Haskell this state is not hidden. It is explicitly said main is a function that potentially changes the state of the world. Its type is then something like:

      +
      main :: World -> World
      +

      Not all functions may have access to this variable. Those which have access to this variable are impure. Functions to which the world variable isn’t provided are pure6.

      +

      Haskell considers the state of the world as an input variable to main. But the real type of main is closer to this one7:

      +
      main :: World -> ((),World)
      +

      The () type is the null type. Nothing to see here.

      +

      Now let’s rewrite our main function with this in mind:

      +
      main w0 =
      +    let (list,w1) = askUser w0 in
      +    let (x,w2) = print (sum list,w1) in
      +    x
      +

      First, we note that all functions which have side effects must have the type:

      +
      World -> (a,World)
      +

      Where a is the type of the result. For example, a getChar function should have the type World -> (Char,World).

      +

      Another thing to note is the trick to fix the order of evaluation. In Haskell, in order to evaluate f a b, you have many choices:

      +
        +
      • first eval a then b then f a b
      • +
      • first eval b then a then f a b.
      • +
      • eval a and b in parallel then f a b
      • +
      +

      This is true, because we should work in a pure language.

      +

      Now, if you look at the main function, it is clear you must eval the first line before the second one since, to evaluate the second line you have to get a parameter given by the evaluation of the first line.

      +

      Such trick works nicely. The compiler will at each step provide a pointer to a new real world id. Under the hood, print will evaluate as:

      +
        +
      • print something on the screen
      • +
      • modify the id of the world
      • +
      • evaluate as ((),new world id).
      • +
      +

      Now, if you look at the style of the main function, it is clearly awkward. Let’s try to do the same to the askUser function:

      +
      askUser :: World -> ([Integer],World)
      +

      Before:

      +
      askUser :: IO [Integer]
      +askUser = do
      +  putStrLn "Enter a list of numbers:"
      +  input <- getLine
      +  let maybeList = getListFromString input in
      +      case maybeList of
      +          Just l  -> return l
      +          Nothing -> askUser
      +

      After:

      +
      askUser w0 =
      +    let (_,w1)     = putStrLn "Enter a list of numbers:" in
      +    let (input,w2) = getLine w1 in
      +    let (l,w3)     = case getListFromString input of
      +                      Just l   -> (l,w2)
      +                      Nothing  -> askUser w2
      +    in
      +        (l,w3)
      +

      This is similar, but awkward. Look at all these temporary w? names.

      +

      The lesson, is, naive IO implementation in Pure functional languages is awkward!

      +

      Fortunately, there is a better way to handle this problem. We see a pattern. Each line is of the form:

      +
      let (y,w') = action x w in
      +

      Even if for some line the first x argument isn’t needed. The output type is a couple, (answer, newWorldValue). Each function f must have a type similar to:

      +
      f :: World -> (a,World)
      +

      Not only this, but we can also note that we always follow the same usage pattern:

      +
      let (y,w1) = action1 w0 in
      +let (z,w2) = action2 w1 in
      +let (t,w3) = action3 w2 in
      +...
      +

      Each action can take from 0 to n parameters. And in particular, each action can take a parameter from the result of a line above.

      +

      For example, we could also have:

      +
      let (_,w1) = action1 x w0   in
      +let (z,w2) = action2 w1     in
      +let (_,w3) = action3 x z w2 in
      +...
      +

      And of course actionN w :: (World) -> (a,World).

      +
      +

      IMPORTANT, there are only two important patterns to consider:

      +
      let (x,w1) = action1 w0 in
      +let (y,w2) = action2 x w1 in
      +

      and

      +
      let (_,w1) = action1 w0 in
      +let (y,w2) = action2 w1 in
      +
      +

      Jocker pencil trick

      +

      Now, we will do a magic trick. We will make the temporary world symbol “disappear”. We will bind the two lines. Let’s define the bind function. Its type is quite intimidating at first:

      +
      bind :: (World -> (a,World))
      +        -> (a -> (World -> (b,World)))
      +        -> (World -> (b,World))
      +

      But remember that (World -> (a,World)) is the type for an IO action. Now let’s rename it for clarity:

      +
      type IO a = World -> (a, World)
      +

      Some example of functions:

      +
      getLine :: IO String
      +print :: Show a => a -> IO ()
      +

      getLine is an IO action which takes a world as parameter and returns a couple (String,World). Which can be summarized as: getLine is of type IO String. Which we also see as, an IO action which will return a String “embeded inside an IO”.

      +

      The function print is also interesting. It takes one argument which can be shown. In fact it takes two arguments. The first is the value to print and the other is the state of world. It then returns a couple of type ((),World). This means it changes the state of the world, but doesn’t yield anymore data.

      +

      This type helps us simplify the type of bind:

      +
      bind :: IO a
      +        -> (a -> IO b)
      +        -> IO b
      +

      It says that bind takes two IO actions as parameter and return another IO action.

      +

      Now, remember the important patterns. The first was:

      +
      let (x,w1) = action1 w0 in
      +let (y,w2) = action2 x w1 in
      +(y,w2)
      +

      Look at the types:

      +
      action1  :: IO a
      +action2  :: a -> IO b
      +(y,w2)   :: IO b
      +

      Doesn’t it seem familiar?

      +
      (bind action1 action2) w0 =
      +    let (x, w1) = action1 w0
      +        (y, w2) = action2 x w1
      +    in  (y, w2)
      +

      The idea is to hide the World argument with this function. Let’s go: As an example imagine if we wanted to simulate:

      +
      let (line1,w1) = getLine w0 in
      +let ((),w2) = print line1 in
      +((),w2)
      +

      Now, using the bind function:

      +
      (res,w2) = (bind getLine (\l -> print l)) w0
      +

      As print is of type (World -> ((),World)), we know res = () (null type). If you didn’t see what was magic here, let’s try with three lines this time.

      +
      let (line1,w1) = getLine w0 in
      +let (line2,w2) = getLine w1 in
      +let ((),w3) = print (line1 ++ line2) in
      +((),w3)
      +

      Which is equivalent to:

      +
      (res,w3) = bind getLine (\line1 ->
      +             bind getLine (\line2 ->
      +               print (line1 ++ line2)))
      +

      Didn’t you notice something? Yes, no temporary World variables are used anywhere! This is MA. GIC.

      +

      We can use a better notation. Let’s use (>>=) instead of bind. (>>=) is an infix function like (+); reminder 3 + 4 ⇔ (+) 3 4

      +
      (res,w3) = getLine >>=
      +           \line1 -> getLine >>=
      +           \line2 -> print (line1 ++ line2)
      +

      Ho Ho Ho! Happy Christmas Everyone! Haskell has made syntactical sugar for us:

      +
      do
      +  x <- action1
      +  y <- action2
      +  z <- action3
      +  ...
      +

      Is replaced by:

      +
      action1 >>= \x ->
      +action2 >>= \y ->
      +action3 >>= \z ->
      +...
      +

      Note you can use x in action2 and x and y in action3.

      +

      But what about the lines not using the <-? Easy, another function blindBind:

      +
      blindBind :: IO a -> IO b -> IO b
      +blindBind action1 action2 w0 =
      +    bind action (\_ -> action2) w0
      +

      I didn’t simplify this definition for clarity purpose. Of course we can use a better notation, we’ll use the (>>) operator.

      +

      And

      +
      do
      +    action1
      +    action2
      +    action3
      +

      Is transformed into

      +
      action1 >>
      +action2 >>
      +action3
      +

      Also, another function is quite useful.

      +
      putInIO :: a -> IO a
      +putInIO x = IO (\w -> (x,w))
      +

      This is the general way to put pure values inside the “IO context”. The general name for putInIO is return. This is quite a bad name when you learn Haskell. return is very different from what you might be used to.

      +
      +

      03_Hell/01_IO/21_Detailled_IO.lhs

      +

      To finish, let’s translate our example:

      +
      
      +askUser :: IO [Integer]
      +askUser = do
      +  putStrLn "Enter a list of numbers (separated by commas):"
      +  input <- getLine
      +  let maybeList = getListFromString input in
      +      case maybeList of
      +          Just l  -> return l
      +          Nothing -> askUser
      +
      +main :: IO ()
      +main = do
      +  list <- askUser
      +  print $ sum list
      +

      Is translated into:

      +
      +
      import Data.Maybe
      +
      +maybeRead :: Read a => String -> Maybe a
      +maybeRead s = case reads s of
      +                  [(x,"")]    -> Just x
      +                  _           -> Nothing
      +getListFromString :: String -> Maybe [Integer]
      +getListFromString str = maybeRead $ "[" ++ str ++ "]"
      +askUser :: IO [Integer]
      +askUser = 
      +    putStrLn "Enter a list of numbers (sep. by commas):" >>
      +    getLine >>= \input ->
      +    let maybeList = getListFromString input in
      +      case maybeList of
      +        Just l -> return l
      +        Nothing -> askUser
      +
      +main :: IO ()
      +main = askUser >>=
      +  \list -> print $ sum list
      +
      +

      You can compile this code to verify it keeps working.

      +

      Imagine what it would look like without the (>>) and (>>=).

      +

      03_Hell/01_IO/21_Detailled_IO.lhs

      +
      +

      03_Hell/02_Monads/10_Monads.lhs

      +

      +Monads +

      + +

      Dali, reve. It represents a weapon out of the mouth of a tiger, itself out of the mouth of another tiger, itself out of the mouth of a fish itself out of a grenade. I could have choosen a picture of the Human centipede as it is a very good representation of what a monad really is. But just to thing about it, I find this disgusting and that wasn

      +

      Now the secret can be revealed: IO is a monad. Being a monad means you have access to some syntactical sugar with the do notation. But mainly, you have access to a coding pattern which will ease the flow of your code.

      +
      +

      Important remarks:

      +
        +
      • Monad are not necessarily about effects! There are a lot of pure monads.
      • +
      • Monad are more about sequencing
      • +
      +
      +

      For the Haskell language Monad is a type class. To be an instance of this type class, you must provide the functions (>>=) and return. The function (>>) will be derived from (>>=). Here is how the type class Monad is declared (mostly):

      +
      class Monad m  where
      +  (>>=) :: m a -> (a -> m b) -> m b
      +  return :: a -> m a
      +
      +  (>>) :: m a -> m b -> m b
      +  f >> g = f >>= \_ -> g
      +
      +  -- You should generally safely ignore this function
      +  -- which I believe exists for historical reason
      +  fail :: String -> m a
      +  fail = error
      +
      +

      Remarks:

      +
        +
      • the keyword class is not your friend. A Haskell class is not a class like in object model. A Haskell class has a lot of similarities with Java interfaces. A better word should have been typeclass. That means a set of types. For a type to belong to a class, all functions of the class must be provided for this type.
      • +
      • In this particular example of type class, the type m must be a type that takes an argument. for example IO a, but also Maybe a, [a], etc…
      • +
      • To be a useful monad, your function must obey some rules. If your construction does not obey these rules strange things might happens:
      • +
      +

      ~ return a >>= k == k a m >>= return == m m >>= (-> k x >>= h) == (m >>= k) >>= h ~

      +
      +

      +Maybe is a monad +

      + +

      There are a lot of different types that are instance of Monad. One of the easiest to describe is Maybe. If you have a sequence of Maybe values, you can use monads to manipulate them. It is particularly useful to remove very deep if..then..else.. constructions.

      +

      Imagine a complex bank operation. You are eligible to gain about 700€ only if you can afford to follow a list of operations without being negative.

      +
      +
      deposit  value account = account + value
      +withdraw value account = account - value
      +
      +eligible :: (Num a,Ord a) => a -> Bool
      +eligible account =
      +  let account1 = deposit 100 account in
      +    if (account1 < 0)
      +    then False
      +    else
      +      let account2 = withdraw 200 account1 in
      +      if (account2 < 0)
      +      then False
      +      else
      +        let account3 = deposit 100 account2 in
      +        if (account3 < 0)
      +        then False
      +        else
      +          let account4 = withdraw 300 account3 in
      +          if (account4 < 0)
      +          then False
      +          else
      +            let account5 = deposit 1000 account4 in
      +            if (account5 < 0)
      +            then False
      +            else
      +              True
      +
      +main = do
      +  print $ eligible 300 -- True
      +  print $ eligible 299 -- False
      +
      +

      03_Hell/02_Monads/10_Monads.lhs

      +
      +

      03_Hell/02_Monads/11_Monads.lhs

      +

      Now, let’s make it better using Maybe and the fact that it is a Monad

      +
      +
      deposit :: (Num a) => a -> a -> Maybe a
      +deposit value account = Just (account + value)
      +
      +withdraw :: (Num a,Ord a) => a -> a -> Maybe a
      +withdraw value account = if (account < value) 
      +                         then Nothing 
      +                         else Just (account - value)
      +
      +eligible :: (Num a, Ord a) => a -> Maybe Bool
      +eligible account = do
      +  account1 <- deposit 100 account 
      +  account2 <- withdraw 200 account1 
      +  account3 <- deposit 100 account2 
      +  account4 <- withdraw 300 account3 
      +  account5 <- deposit 1000 account4
      +  Just True
      +
      +main = do
      +  print $ eligible 300 -- Just True
      +  print $ eligible 299 -- Nothing
      +
      +

      03_Hell/02_Monads/11_Monads.lhs

      +
      +

      03_Hell/02_Monads/12_Monads.lhs

      +

      Not bad, but we can make it even better:

      +
      +
      deposit :: (Num a) => a -> a -> Maybe a
      +deposit value account = Just (account + value)
      +
      +withdraw :: (Num a,Ord a) => a -> a -> Maybe a
      +withdraw value account = if (account < value) 
      +                         then Nothing 
      +                         else Just (account - value)
      +
      +eligible :: (Num a, Ord a) => a -> Maybe Bool
      +eligible account =
      +  deposit 100 account >>=
      +  withdraw 200 >>=
      +  deposit 100  >>=
      +  withdraw 300 >>=
      +  deposit 1000 >>
      +  return True
      +
      +main = do
      +  print $ eligible 300 -- Just True
      +  print $ eligible 299 -- Nothing
      +
      +

      We have proven that Monads are a good way to make our code more elegant. Note this idea of code organization, in particular for Maybe can be used in most imperative language. In fact, this is the kind of construction we make naturally.

      +
      +

      An important remark:

      +

      The first element in the sequence being evaluated to Nothing will stop the complete evaluation. This means you don’t execute all lines. You have this for free, thanks to laziness.

      +
      +

      You could also replay these example with the definition of (>>=) for Maybe in mind:

      +
      instance Monad Maybe where
      +    (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
      +    Nothing  >>= _  = Nothing
      +    (Just x) >>= f  = f x
      +
      +    return x = Just x
      +

      The Maybe monad proved to be useful while being a very simple example. We saw the utility of the IO monad. But now a cooler example, lists.

      +

      03_Hell/02_Monads/12_Monads.lhs

      +
      +

      03_Hell/02_Monads/13_Monads.lhs

      +

      +The list monad +

      + +

      Golconde de Magritte

      +

      The list monad helps us to simulate non deterministic computations. Here we go:

      +
      +
      import Control.Monad (guard)
      +
      +allCases = [1..10]
      +
      +resolve :: [(Int,Int,Int)]
      +resolve = do
      +              x <- allCases
      +              y <- allCases
      +              z <- allCases
      +              guard $ 4*x + 2*y < z
      +              return (x,y,z)
      +
      +main = do
      +  print resolve
      +
      +

      MA. GIC. :

      +
      [(1,1,7),(1,1,8),(1,1,9),(1,1,10),(1,2,9),(1,2,10)]
      +

      For the list monad, there is also a syntactical sugar:

      +
      +
        print $ [ (x,y,z) | x <- allCases,
      +                      y <- allCases,
      +                      z <- allCases,
      +                      4*x + 2*y < z ]
      +
      +

      I won’t list all the monads, but there are many monads. Using monads simplifies the manipulation of several notions in pure languages. In particular, monad are very useful for:

      +
        +
      • IO,
      • +
      • non deterministic computation,
      • +
      • generating pseudo random numbers,
      • +
      • keeping configuration state,
      • +
      • writing state,
      • +
      • +
      +

      If you have followed me until here, then you’ve done it! You know monads8!

      +

      03_Hell/02_Monads/13_Monads.lhs

      +

      +Appendix +

      + +

      This section is not so much about learning Haskell. It is just here to discuss some details further.

      +
      +

      04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs

      +

      +More on Infinite Tree +

      + +

      In the section Infinite Structures we saw some simple constructions. Unfortunately we removed two properties from our tree:

      +
        +
      1. no duplicate node value
      2. +
      3. well ordered tree
      4. +
      +

      In this section we will try to keep the first property. Concerning the second one, we must relax it but we’ll discuss how to keep it as much as possible.

      +
      + +

      This code is mostly the same as the one in the tree section.

      +
      +
      import Data.List
      +data BinTree a = Empty 
      +                 | Node a (BinTree a) (BinTree a) 
      +                  deriving (Eq,Ord)
      +
      +-- declare BinTree a to be an instance of Show
      +instance (Show a) => Show (BinTree a) where
      +  -- will start by a '<' before the root
      +  -- and put a : a begining of line
      +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
      +    where
      +    treeshow pref Empty = ""
      +    treeshow pref (Node x Empty Empty) = 
      +                  (pshow pref x)
      +
      +    treeshow pref (Node x left Empty) = 
      +                  (pshow pref x) ++ "\n" ++
      +                  (showSon pref "`--" "   " left)
      +
      +    treeshow pref (Node x Empty right) = 
      +                  (pshow pref x) ++ "\n" ++
      +                  (showSon pref "`--" "   " right)
      +
      +    treeshow pref (Node x left right) = 
      +                  (pshow pref x) ++ "\n" ++
      +                  (showSon pref "|--" "|  " left) ++ "\n" ++
      +                  (showSon pref "`--" "   " right)
      +
      +    -- show a tree using some prefixes to make it nice
      +    showSon pref before next t = 
      +                  pref ++ before ++ treeshow (pref ++ next) t
      +
      +    -- pshow replace "\n" by "\n"++pref
      +    pshow pref x = replace '\n' ("\n"++pref) (show x)
      +
      +    -- replace on char by another string
      +    replace c new string =
      +      concatMap (change c new) string
      +      where
      +          change c new x 
      +              | x == c = new
      +              | otherwise = x:[] -- "x"
      +
      +
      + +

      Our first step is to create some pseudo-random number list:

      +
      +
      shuffle = map (\x -> (x*3123) `mod` 4331) [1..]
      +
      +

      Just as a reminder, here is the definition of treeFromList

      +
      +
      treeFromList :: (Ord a) => [a] -> BinTree a
      +treeFromList []    = Empty
      +treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
      +                             (treeFromList (filter (>x) xs))
      +
      +

      and treeTakeDepth:

      +
      +
      treeTakeDepth _ Empty = Empty
      +treeTakeDepth 0 _     = Empty
      +treeTakeDepth n (Node x left right) = let
      +          nl = treeTakeDepth (n-1) left
      +          nr = treeTakeDepth (n-1) right
      +          in
      +              Node x nl nr
      +
      +

      See the result of:

      +
      +
      main = do
      +      putStrLn "take 10 shuffle"
      +      print $ take 10 shuffle
      +      putStrLn "\ntreeTakeDepth 4 (treeFromList shuffle)"
      +      print $ treeTakeDepth 4 (treeFromList shuffle)
      +
      +
      % runghc 02_Hard_Part/41_Infinites_Structures.lhs
      +take 10 shuffle
      +[3123,1915,707,3830,2622,1414,206,3329,2121,913]
      +treeTakeDepth 4 (treeFromList shuffle)
      +
      +< 3123
      +: |--1915
      +: |  |--707
      +: |  |  |--206
      +: |  |  `--1414
      +: |  `--2622
      +: |     |--2121
      +: |     `--2828
      +: `--3830
      +:    |--3329
      +:    |  |--3240
      +:    |  `--3535
      +:    `--4036
      +:       |--3947
      +:       `--4242
      +

      Yay! It ends! Beware though, it will only work if you always have something to put into a branch.

      +

      For example

      +
      treeTakeDepth 4 (treeFromList [1..]) 
      +

      will loop forever. Simply because it will try to access the head of filter (<1) [2..]. But filter is not smart enought to understand that the result is the empty list.

      +

      Nonetheless, it is still a very cool example of what non strict programs have to offer.

      +

      Left as an exercise to the reader:

      +
        +
      • Prove the existence of a number n so that treeTakeDepth n (treeFromList shuffle) will enter an infinite loop.
      • +
      • Find an upper bound for n.
      • +
      • Prove there is no shuffle list so that, for any depth, the program ends.
      • +
      +

      04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs

      +
      +

      04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs

      +
      + +

      This code is mostly the same as the preceding one.

      +
      +
      import Debug.Trace (trace)
      +import Data.List
      +data BinTree a = Empty 
      +                 | Node a (BinTree a) (BinTree a) 
      +                  deriving (Eq,Ord)
      +
      +
      +
      -- declare BinTree a to be an instance of Show
      +instance (Show a) => Show (BinTree a) where
      +  -- will start by a '<' before the root
      +  -- and put a : a begining of line
      +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
      +    where
      +    treeshow pref Empty = ""
      +    treeshow pref (Node x Empty Empty) = 
      +                  (pshow pref x)
      +
      +    treeshow pref (Node x left Empty) = 
      +                  (pshow pref x) ++ "\n" ++
      +                  (showSon pref "`--" "   " left)
      +
      +    treeshow pref (Node x Empty right) = 
      +                  (pshow pref x) ++ "\n" ++
      +                  (showSon pref "`--" "   " right)
      +
      +    treeshow pref (Node x left right) = 
      +                  (pshow pref x) ++ "\n" ++
      +                  (showSon pref "|--" "|  " left) ++ "\n" ++
      +                  (showSon pref "`--" "   " right)
      +
      +    -- show a tree using some prefixes to make it nice
      +    showSon pref before next t = 
      +                  pref ++ before ++ treeshow (pref ++ next) t
      +
      +    -- pshow replace "\n" by "\n"++pref
      +    pshow pref x = replace '\n' ("\n"++pref) (" " ++ show x)
      +
      +    -- replace on char by another string
      +    replace c new string =
      +      concatMap (change c new) string
      +      where
      +          change c new x 
      +              | x == c = new
      +              | otherwise = x:[] -- "x"
      +
      +treeTakeDepth _ Empty = Empty
      +treeTakeDepth 0 _     = Empty
      +treeTakeDepth n (Node x left right) = let
      +          nl = treeTakeDepth (n-1) left
      +          nr = treeTakeDepth (n-1) right
      +          in
      +              Node x nl nr
      +
      +
      + +

      In order to resolve these problem we will modify slightly our treeFromList and shuffle function.

      +

      A first problem, is the lack of infinite different number in our implementation of shuffle. We generated only 4331 different numbers. To resolve this we make a slightly better shuffle function.

      +
      +
      shuffle = map rand [1..]
      +          where 
      +              rand x = ((p x) `mod` (x+c)) - ((x+c) `div` 2)
      +              p x = m*x^2 + n*x + o -- some polynome
      +              m = 3123    
      +              n = 31
      +              o = 7641
      +              c = 1237
      +
      +

      This shuffle function has the property (hopefully) not to have an upper nor lower bound. But having a better shuffle list isn’t enough not to enter an infinite loop.

      +

      Generally, we cannot decide whether filter (<x) xs is empty. Then to resolve this problem, I’ll authorize some error in the creation of our binary tree. This new version of code can create binary tree which don’t have the following property for some of its nodes:

      +
      +

      Any element of the left (resp. right) branch must all be strictly inferior (resp. superior) to the label of the root.

      +
      +

      Remark it will remains mostly an ordered binary tree. Furthermore, by construction, each node value is unique in the tree.

      +

      Here is our new version of treeFromList. We simply have replaced filter by safefilter.

      +
      +
      treeFromList :: (Ord a, Show a) => [a] -> BinTree a
      +treeFromList []    = Empty
      +treeFromList (x:xs) = Node x left right
      +          where 
      +              left = treeFromList $ safefilter (<x) xs
      +              right = treeFromList $ safefilter (>x) xs
      +
      +

      This new function safefilter is almost equivalent to filter but don’t enter infinite loop if the result is a finite list. If it cannot find an element for which the test is true after 10000 consecutive steps, then it considers to be the end of the search.

      +
      +
      safefilter :: (a -> Bool) -> [a] -> [a]
      +safefilter f l = safefilter' f l nbTry
      +  where
      +      nbTry = 10000
      +      safefilter' _ _ 0 = []
      +      safefilter' _ [] _ = []
      +      safefilter' f (x:xs) n = 
      +                  if f x 
      +                     then x : safefilter' f xs nbTry 
      +                     else safefilter' f xs (n-1) 
      +
      +

      Now run the program and be happy:

      +
      +
      main = do
      +      putStrLn "take 10 shuffle"
      +      print $ take 10 shuffle
      +      putStrLn "\ntreeTakeDepth 8 (treeFromList shuffle)"
      +      print $ treeTakeDepth 8 (treeFromList $ shuffle)
      +
      +

      You should realize the time to print each value is different. This is because Haskell compute each value when it needs it. And in this case, this is when asked to print it on the screen.

      +

      Impressively enough, try to replace the depth from 8 to 100. It will work without killing your RAM! The flow and the memory management is done naturally by Haskell.

      +

      Left as an exercise to the reader:

      +
        +
      • Even with large constant value for deep and nbTry, it seems to work nicely. But in the worst case, it can be exponential. Create a worst case list to give as parameter to treeFromList.
        hint: think about ([0,-1,-1,....,-1,1,-1,...,-1,1,...]).
      • +
      • I first tried to implement safefilter as follow: +
        +  safefilter' f l = if filter f (take 10000 l) == []
        +                    then []
        +                    else filter f l
        +  
        + +Explain why it doesn’t work and can enter into an infinite loop.
      • +
      • Suppose that shuffle is real random list with growing bounds. If you study a bit this structure, you’ll discover that with probability 1, this structure is finite. Using the following code (suppose we could use safefilter' directly as if was not in the where of safefilter) find a definition of f such that with probability 1, treeFromList’ shuffle is infinite. And prove it. Disclaimer, this is only a conjecture.
      • +
      +
      treeFromList' []  n = Empty
      +treeFromList' (x:xs) n = Node x left right
      +    where
      +        left = treeFromList' (safefilter' (<x) xs (f n)
      +        right = treeFromList' (safefilter' (>x) xs (f n)
      +        f = ???
      +

      04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs

      +

      Thanks

      +

      Thanks to /r/haskell and /r/programming. Your comment were most than welcome.

      +

      Particularly, I want to thank Emm a thousand times for the time he spent on correcting my English. Thank you man.

      +
      +
      +
        +
      1. Even if most recent languages try to hide them, they are present.

      2. +
      3. I know I’m cheating. But I will talk about non-strict later.

      4. +
      5. For the brave, a more complete explanation of pattern matching can be found here.

      6. +
      7. You should remark squareEvenSum'' is more efficient that the two other versions. The order of (.) is important.

      8. +
      9. Which itself is very similar to the javascript eval on a string containing JSON).

      10. +
      11. There are some unsafe exceptions to this rule. But you shouldn’t see such use on a real application except maybe for debugging purpose.

      12. +
      13. For the curious the real type is data IO a = IO {unIO :: State# RealWorld -> (# State# RealWorld, a #)}. All the # as to do with optimisation and I swapped the fields in my example. But mostly, the idea is exactly the same.

      14. +
      15. Well, you’ll certainly need to practice a bit to get used to them and to understand when you can use them and create your own. But you already made a big step in this direction.

      16. +
      +
      ]]>
      +
      + + Typography and the Web + + http://yannesposito.com/Scratch/en/blog/Typography-and-the-Web/index.html + 2012-02-02T00:00:00Z + 2012-02-02T00:00:00Z +

      +
      + +

      tl;dr: Web typography sucks and we’ll have to wait forever before it will be fixed.

      +
      + +

      I stumbled upon open typography. Their main message is:

      +
      +

      «There is no reason to wait for browser development to catch up. We can all create better web typography ourselves, today.»

      +
      +

      As somebody who tried to make my website using some nice typography features and in particular ligatures, I believe this is wrong.

      +

      I already made an automatic system which will detect and replace text by their ligatures in my blog. But this I never published this on the web and this is why.

      +

      First, what is a ligatures?

      +

      +

      What is the problem between the Web and ligatures? The first one is: you cannot search them. For example, try to search the word “first”:

      +
        +
      • first ← No ligature, no problem1
      • +
      • r ← ligature nice but unsearchable
      • +
      +

      The second one is the rendering, for example, try to use a ligature character with small caps:

      +
        +
      • first
      • +
      • r
      • +
      +

      Here is a screenshot of what I see:

      +

      +

      The browser isn’t able to understand that the ligature character “” should render as fi when rendered in small caps. And one part of the problem is you should choose to display a character in small caps using css.

      +

      This way, how could you use a ligature Unicode character on a site on which you could change the css?

      +

      Let’s compare to LaTeX.

      +

      +

      If you take attention to detail, you’ll see the first “first” contains a ligature. Of course the second render nicely. The code I used were:

      +
      \item first
      +\item {\sc first}
      +

      LaTeX was intelligent enough to create himself the ligatures when needed.

      +

      The “” ligature is rare and not rendered in LaTeX by default. But if you want you could also render rare ligature using XƎLaTeX:

      +

      XeLaTeX ligatures

      +

      I took this image from the excellent article of Dario Taraborelli.

      +

      Clearly fix the rendering of ligature in a browser is a difficult task. Simply imagine the number of strange little exceptions:

      +
        +
      • The text is rendered in small caps, I cannot use ligature.
      • +
      • The current word contains a ligature unicode character, I should search for ligature in this one.
      • +
      • The current font does not defined the ligature unicode character, we shouldn’t use it, etc
      • +
      • A javascript command changed the CSS, I should verify if I had to revert the insertion of ligatures characters
      • +
      • etc…
      • +
      +

      Nonetheless if someone has a solution, I would be happy to hear about it.

      +
      +
      +
        +
      1. In fact, you might see a ligature and the search works because I now use some CSS ninja skills: text-rendering: optimizelegibility. But it also works because I use the right font; Computer Modern. Steal my CSS at will.

      2. +
      +
      ]]>
      +
      + + Haskell web programming + + http://yannesposito.com/Scratch/en/blog/Yesod-tutorial-for-newbies/index.html + 2012-01-15T00:00:00Z + 2012-01-15T00:00:00Z + Neo Flying at warp speed

      +
      + +

      update: updated for yesod 0.10

      +

      tl;dr: A simple yesod tutorial. Yesod is a Haskell web framework. You shouldn’t need to know Haskell.

      +
      +
      +Table of content +
      + +
        +
      • Table of Content (generated) {:toc}
      • +
      +
      +
      + +

      Why Haskell?

      +

      Impressive Haskell Benchmark

      +

      Its efficiency (see [Snap Benchmark][snapbench] & Warp Benchmark[^benchmarkdigression]). Haskell is an order of magnitude faster than interpreted languages like [Ruby][haskellvsruby] and [Python][haskellvspython][^speeddigression].

      +

      Haskell is a high level language and make it harder to shoot you in the foot than C, C++ or Java for example. One of the best property of Haskell being:

      +
      +

      “If your program compile it will be very close to what the programmer intended”.

      +
      +

      Haskell web frameworks handle parallel tasks perfectly. For example even better than node.js[^nodejstroll].

      +

      Thousands of Agent Smith

      +

      From the pure technical point of view, Haskell seems to be the perfect web development tool. Weaknesses of Haskell certainly won’t be technical:

      +
        +
      • Hard to grasp Haskell
      • +
      • Hard to find a Haskell programmer
      • +
      • The Haskell community is smaller than the community for /.*/
      • +
      • There is no heroku for Haskell (even if Greg Weber did it, it was more a workaround).
      • +
      +

      I won’t say these are not important drawbacks. But, with Haskell your web application will have both properties to absorb an impressive number of parallel request securely and to adapt to change.

      +

      Actually there are three main Haskell web frameworks:

      +
        +
      1. Happstack
      2. +
      3. Snap
      4. +
      5. Yesod
      6. +
      +

      I don’t think there is a real winner between these three framework. The choice I made for yesod is highly subjective. I just lurked a bit and tried some tutorials. I had the feeling yesod make a better job at helping newcomers. Furthermore, apparently the yesod team seems the most active. Of course I might be wrong since it is a matter of feeling.

      +

      1. Draw some circles. 2. Draw the rest of the fucking owl

      +

      Why did I write this article? The yesod documentation and particularly the book are excellent. But I missed an intermediate tutorial. This tutorial won’t explain all details. I tried to give a step by step of how to start from a five minute tutorial to an almost production ready architecture. Furthermore explaining something to others is a great way to learn. If you are used to Haskell and Yesod, this tutorial won’t learn you much. If you are completely new to Haskell and Yesod it might hopefully helps you. Also if you find yourself too confused by the syntax, it might helps to read this article

      +

      During this tutorial you’ll install, initialize and configure your first yesod project. Then there is a very minimal 5 minutes yesod tutorial to heat up and verify the awesomeness of yesod. Then we will clean up the 5 minutes tutorial to use some “best practices”. Finally there will be a more standard real world example; a minimal blog system.

      +

      [snapbench]: http://snapframework.com/blog/2010/11/17/snap-0.3-benchmarks [^benchmarkdigression]: One can argue these benchmark contains many problems. But the benchmarks are just here to give an order of idea. Mainly Haskell is very fast. [^speeddigression]: Generally high level Haskell is slower than C, but low level Haskell is equivalent to C speed. It means that even if you can easily link C code with Haskell, this is not needed to reach the same speed. Furthermore writing a web service in C/C++ seems to be a very bad idea. You can take a look at a discussion on HN about this. [^nodejstroll]: If you are curious, you can search about the Fibonacci node.js troll. Without any tweaking, Haskell handled this problem perfectly. I tested it myself using yesod instead of Snap. [haskellvsruby]: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=ghc&lang2=yarv [haskellvspython]: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=ghc&lang2=python3

      +

      Before the real start

      +

      Install

      +

      The recommended way to install Haskell is to download the Haskell Platform.

      +

      Once done, you need to install yesod. Open a terminal session and do:

      +
      ~ cabal update
      +~ cabal install yesod cabal-dev
      +

      There are few steps but it should take some time to finish.

      +

      Initialize

      +

      You are now ready to initialize your first yesod project. Open a terminal and type:

      +
      ~ yesod init
      +

      Enter your name, choose yosog for the project name and enter Yosog for the name of the Foundation. Finally choose sqlite. Now, start the development cycle:

      +
      ~ cd yosog
      +~ cabal-dev install && yesod --dev devel
      +

      This will compile the entire project. Be patient it could take a while the first time. Once finished a server is launched and you could visit it by clicking this link:

      +

      http://localhost:3000

      +

      Congratulation! Yesod works!

      +
      + +

      Note: if something is messed up use the following command line inside the project directory.

      +
      \rm -rf dist/* ; cabal-dev install && yesod --dev devel
      +
      + +

      Until the end of the tutorial, use another terminal and let this one open in a corner to see what occurs.

      +

      Configure git

      +
      +

      Of course this step is not mandatory for the tutorial but it is a good practice.

      +
      +

      Copy this .gitignore file into the yosog folder.

      +
      cabal-dev
      +dist
      +.static-cache
      +static/tmp
      +*.sqlite3
      +

      Then initialize your git repository:

      +
      ~ git init .
      +~ git add .
      +~ git commit -a -m "Initial yesod commit"
      +

      We are almost ready to start.

      +

      Some last minute words

      +

      Up until here, we have a directory containing a bunch of files and a local web server listening the port 3000. If we modify a file inside this directory, yesod should try to recompile as fast as possible the site. Instead of explaining the role of every file, let’s focus only on the important files/directories for this tutorial:

      +
        +
      1. config/routes
      2. +
      3. Handler/
      4. +
      5. templates/
      6. +
      7. config/models
      8. +
      +

      Obviously:

      +

      config/routes | is where you’ll configure the map %url → Code. |
      Handler/ | contains the files that will contain the code called when a %url is accessed. |
      templates/ | contains html, js and css templates. |
      config/models | is where you’ll configure the persistent objects (database tables). |

      +

      During this tutorial we’ll modify other files as well, but we won’t explore them in detail.

      +

      Also note, shell commands are executed in the root directory of your project instead specified otherwise.

      +

      We are now ready to start!

      +

      Echo

      +

      To verify the quality of the security of the yesod framework, let’s make a minimal echo application.

      +
      +

      Goal:

      +

      Make a server that when accessed /echo/[some text] should return a web page containing “some text” inside an h1 bloc.

      +
      +

      In a first time, we must declare the %url of the form /echo/... are meaningful. Let’s take a look at the file config/routes:

      +
      +/static StaticR Static getStatic
      +/auth   AuthR   Auth   getAuth
      +
      +/favicon.ico FaviconR GET
      +/robots.txt RobotsR GET
      +
      +/ HomeR GET
      +
      + +

      We want to add a route of the form /echo/[anything] somehow and do some action with this. Add the following:

      +
      +/echo/#String EchoR GET
      +
      + +

      This line contains three elements: the %url pattern, a handler name, an %http method. I am not particularly fan of the big R notation but this is the standard convention.

      +

      If you save config/routes, you should see your terminal in which you launched yesod devel activate and certainly displaying an error message.

      +
      +Application.hs:31:1: Not in scope: `getEchoR'
      +
      + +

      Why? Simply because we didn’t written the code for the handler EchoR. Edit the file Handler/Home.hs and append this:

      +
      getEchoR :: String -> Handler RepHtml
      +getEchoR theText = do
      +    defaultLayout $ do
      +        [whamlet|<h1>#{theText}|]
      +

      Don’t worry if you find all of this a bit cryptic. In short it just declare a function named getEchoR with one argument (theText) of type String. When this function is called, it return a Handler RepHtml whatever it is. But mainly this will encapsulate our expected result inside an html text.

      +

      After saving the file, you should see yesod recompile the application. When the compilation is finished you’ll see the message: Starting devel application.

      +

      Now you can visit: http://localhost:3000/echo/Yesod%20rocks!

      +

      TADA! It works!

      +

      Bulletproof?

      +

      Neo stops a myriad of bullets

      +

      Even this extremely minimal web application has some impressive properties. For exemple, imagine an attacker entering this %url:

      +[http://localhost:3000/echo/<a>I'm <script>alert("Bad!");](http://localhost:3000/echo/I’m + +

      " %>

      +

      The special characters are protected for us. A malicious user could not hide some bad script inside.

      +

      This behavior is a direct consequence of type safety. The %url string is put inside a %url type. Then the interesting part in the %url is put inside a String type. To pass from %url type to String type some transformation are made. For example, replace all “%20” by space characters. Then to show the String inside an html document, the string is put inside an html type. Some transformations occurs like replace “<” by “&lt;”. Thanks to yesod, this tedious job is done for us.

      +
      "http://localhost:3000/echo/some%20text<a>" :: URL
      +                    ↓
      +              "some text<a>"                 :: String
      +                    ↓
      +          "some text &amp;lt;a&amp;gt;"             :: Html 
      +

      Yesod is not only fast, it helps us to remain secure. It protects us from many common errors in other paradigms. Yes, I am looking at you PHP!

      +

      Cleaning up

      +

      Even this very minimal example should be enhanced. We will clean up many details:

      +
      +

      Use a better css

      +

      It is nice to note, the default template is based on %html5 boilerplate. Let’s change the default css. Add a file named default-layout.lucius inside the templates/ directory containing:

      +
      body {
      +    font-family: Helvetica, sans-serif; 
      +    font-size: 18px; }
      +#main {
      +    padding: 1em;
      +    border: #CCC solid 2px;
      +    border-radius: 5px;
      +    margin: 1em;
      +    width: 37em;
      +    margin: 1em auto;
      +    background: #F2F2F2;
      +    line-height: 1.5em;
      +    color: #333; }
      +.required { margin: 1em 0; }
      +.optional { margin: 1em 0; }
      +label { width: 8em; display: inline-block; }
      +input, textarea { background: #FAFAFA}
      +textarea { width: 27em; height: 9em;}
      +ul { list-style: square; }
      +a { color: #A56; }
      +a:hover { color: #C58; }
      +a:active { color: #C58; }
      +a:visited { color: #943; }
      +

      Personally I would prefer if such a minimal css was put with the scaffolding tool. I am sure somebody already made such a minimal css which give the impression the browser handle correctly html without any style applied to it. But I digress.

      +

      Separate Handlers

      +

      Generally you don’t want to have all your code inside a unique file. This is why we will separate our handlers. In a first time create a new file Handler/Echo.hs containing:

      +
      module Handler.Echo where
      +
      +import Import
      +
      +getEchoR :: String -> Handler RepHtml
      +getEchoR theText = do
      +    defaultLayout $ do
      +        [whamlet|<h1>#{theText}|]
      +

      Do not forget to remove the getEchoR function inside Handler/Home.hs.

      +

      We must declare this new file intoyosog.cabal. Just after Handler.Home, add:

      +
      +    Handler.Echo
      +
      + +

      We must also declare this new Handler module inside Application.hs. Just after the “import Handler.Home”, add:

      +
      import Handler.Echo
      +

      This is it.

      +

      ps: I am sure not so far in the future we could simply write yesod add-handler Echo to declare it and create a new handler file.

      +

      Data.Text

      +

      It is a good practice to use Data.Text instead of String.

      +

      To declare it, add this import directive to Foundation.hs (just after the last one):

      +
      import Data.Text
      +

      We have to modify config/routes and our handler accordingly. Replace #String by #Text in config/routes:

      +
      +/echo/#Text EchoR GET
      +
      + +

      And do the same in Handler/Echo.hs:

      +
      module Handler.Echo where
      +
      +import Import
      +
      +getEchoR :: Text -> Handler RepHtml
      +getEchoR theText = do
      +    defaultLayout $ do
      +        [whamlet|<h1>#{theText}|]
      +

      Use templates

      +

      Some html (more precisely hamlet) is written directly inside our handler. We should put this part inside another file. Create the new file templates/echo.hamlet containing:

      +
      <h1> #{theText}
      +

      and modify the handler Handler/Echo.hs:

      +
      getEchoR :: Text -> Handler RepHtml
      +getEchoR theText = do
      +    defaultLayout $ do
      +        $(widgetFile "echo")
      +

      At this point, our web application is structured between different files. Handler are grouped, we use Data.Text and our views are in templates. It is the time to make a slightly more complex example.

      +

      Mirror

      +

      Neo touching a mirror

      +

      Let’s make another minimal application. You should see a form containing a text field and a validation button. When you enter some text (for example “Jormungad”) and validate, the next page present you the content and its reverse appended to it. In our example it should return “JormungaddagnumroJ”.

      +

      First, add a new route:

      +
      +/mirror MirrorR GET POST
      +
      + +

      This time the path /mirror will accept GET and POST requests. Add the corresponding new Handler file:

      +
      module Handler.Mirror where
      +
      +import Import
      +import qualified Data.Text as T
      +
      +getMirrorR :: Handler RepHtml
      +getMirrorR = do
      +    defaultLayout $ do
      +        $(widgetFile "mirror")
      +
      +postMirrorR :: Handler RepHtml
      +postMirrorR =  do
      +        postedText <- runInputPost $ ireq textField "content"
      +        defaultLayout $ do
      +            $(widgetFile "posted")
      +

      Don’t forget to declare it inside yosog.cabal and Application.hs.

      +

      We will need to use the reverse function provided by Data.Text which explain the additional import.

      +

      The only new thing here is the line that get the POST parameter named “content”. If you want to know more detail about it and form in general you can take look at the yesod book.

      +

      Create the two corresponding templates:

      +
      <h1> Enter your text
      +<form method=post action=@{MirrorR}>
      +    <input type=text name=content>
      +    <input type=submit>
      +
      <h1>You've just posted
      +<p>#{postedText}#{T.reverse postedText}
      +<hr>
      +<p><a href=@{MirrorR}>Get back
      +

      And that is all. This time, we won’t need to clean up. We may have used another way to generate the form but we’ll see this in the next section.

      +

      Just try it by clicking here.

      +

      Also you can try to enter strange values. Like before, your application is quite secure.

      +

      A Blog

      +

      We saw how to retrieve %http parameters. It is the time to save things into a database.

      +

      As before add some routes inside config/routes:

      +
      +/blog               BlogR       GET POST
      +/blog/#ArticleId    ArticleR    GET
      +
      + +

      This example will be very minimal:

      +
        +
      • GET on /blog should display the list of articles.
      • +
      • POST on /blog should create a new article
      • +
      • GET on /blog/<article id> should display the content of the article.
      • +
      +

      First we declare another model object. Append the following content to config/models:

      +
      +Article
      +    title   Text
      +    content Html 
      +    deriving
      +
      + +

      As Html is not an instance of Read, Show and Eq, we had to add the deriving line. If you forget it, there will be an error.

      +

      After the route and the model, we write the handler. First, declare a new Handler module. Add import Handler.Blog inside Application.hs and add it into yosog.cabal. Let’s write the content of Handler/Blog.hs. We start by declaring the module and by importing some block necessary to handle Html in forms.

      +
      module Handler.Blog
      +    ( getBlogR
      +    , postBlogR
      +    , getArticleR
      +    )
      +where
      +
      +import Import
      +import Data.Monoid
      +
      +-- to use Html into forms
      +import Yesod.Form.Nic (YesodNic, nicHtmlField)
      +instance YesodNic App
      +

      Remark: it is a best practice to add the YesodNic instance inside Foundation.hs. I put this definition here to make things easier but you should see a warning about this orphan instance. To put the include inside Foundation.hs is left as an exercice to the reader.

      +

      Hint: Do not forget to put YesodNic and nicHtmlField inside the exported objects of the module.

      +
      entryForm :: Form Article
      +entryForm = renderDivs $ Article
      +    <$> areq   textField "Title" Nothing
      +    <*> areq   nicHtmlField "Content" Nothing
      +

      This function defines a form for adding a new article. Don’t pay attention to all the syntax. If you are curious you can take a look at Applicative Functor. You just have to remember areq is for required form input. Its arguments being: areq type label default_value.

      +
      -- The view showing the list of articles
      +getBlogR :: Handler RepHtml
      +getBlogR = do
      +    -- Get the list of articles inside the database.
      +    articles <- runDB $ selectList [] [Desc ArticleTitle]
      +    -- We'll need the two "objects": articleWidget and enctype
      +    -- to construct the form (see templates/articles.hamlet).
      +    (articleWidget, enctype) <- generateFormPost entryForm
      +    defaultLayout $ do
      +        $(widgetFile "articles")
      +

      This handler should display a list of articles. We get the list from the DB and we construct the form. Just take a look at the corresponding template:

      +
      <h1> Articles
      +$if null articles
      +    -- Show a standard message if there is no article
      +    <p> There are no articles in the blog
      +$else
      +    -- Show the list of articles
      +    <ul>
      +        $forall Entity articleId article <- articles
      +            <li> 
      +                <a href=@{ArticleR articleId} > #{articleTitle article}
      +<hr>
      +  <form method=post enctype=#{enctype}>
      +    ^{articleWidget}
      +    <div>
      +        <input type=submit value="Post New Article">
      +

      You should remark we added some logic inside the template. There is a test and a “loop”.

      +

      Another very interesting part is the creation of the form. The articleWidget was created by yesod. We have given him the right parameters (input required or optional, labels, default values). And now we have a protected form made for us. But we have to create the submit button.

      +

      Get back to Handler/Blog.hs.

      +
      -- we continue Handler/Blog.hs
      +postBlogR :: Handler RepHtml
      +postBlogR = do
      +    ((res,articleWidget),enctype) <- runFormPost entryForm
      +    case res of 
      +         FormSuccess article -> do 
      +            articleId <- runDB $ insert article
      +            setMessage $ toHtml $ (articleTitle article) <> " created"
      +            redirect $ ArticleR articleId 
      +         _ -> defaultLayout $ do
      +                setTitle "Please correct your entry form"
      +                $(widgetFile "articleAddError")
      +

      This function should be used to create a new article. We handle the form response. If there is an error we display an error page. For example if we left some required value blank. If things goes right:

      +
        +
      • we add the new article inside the DB (runDB $ insert article)
      • +
      • we add a message to be displayed (setMessage $ ...)
      • +
      • we are redirected to the article web page.
      • +
      +

      Here is the content of the error Page:

      +
      <form method=post enctype=#{enctype}>
      +    ^{articleWidget}
      +    <div>
      +        <input type=submit value="Post New Article">
      +

      Finally we need to display an article:

      +
      getArticleR :: ArticleId -> Handler RepHtml
      +getArticleR articleId = do
      +    article <- runDB $ get404 articleId
      +    defaultLayout $ do
      +        setTitle $ toHtml $ articleTitle article
      +        $(widgetFile "article")
      +

      The get404 function try to do a get on the DB. If it fails it return a 404 page. The rest should be clear. Here is the content of templates/article.hamlet:

      +
      <h1> #{articleTitle article}
      +<article> #{articleContent article}
      +

      The blog system is finished. Just for fun, you can try to create an article with the following content:

      +
      <p>A last try to <em>cross script</em> 
      +   and <em>SQL injection</em></p>
      +<p>Here is the first try: 
      +   <script>alert("You loose");</script></p>
      +<p> And Here is the last </p>
      +"); DROP TABLE ARTICLE;;
      +

      Conclusion

      +

      This is the end of this tutorial. I made it very minimal.

      +

      If you already know Haskell and you want to go further, you should take a look at the recent i18n blog tutorial. It will be obvious I inspired my own tutorial on it. You’ll learn in a very straightforward way how easy it is to use authorizations, Time and internationalization.

      +

      If, on the other hand you don’t know Haskell. Then you shouldn’t jump directly to web programming. Haskell is a very complex and unusual language. My advice to go as fast as possible in using Haskell for web programming is:

      +
        +
      1. Start by try Haskell in your browser
      2. +
      3. Then read the excellent Learn you a Haskell for Great Good
      4. +
      5. If you have difficulties in understanding concepts like monads, you should really read these articles. For me they were enlightening.
      6. +
      7. If you feel confident, you should be able to follows the yesod book and if you find difficult to follows the yesod book, you should read real world Haskell first (it is a must read).
      8. +
      +

      Also, note that:

      +
        +
      • haskell.org is full of excellent resources.
      • +
      • hoogle will be very useful
      • +
      • Use hlint as soon as possible to get good habits.
      • +
      +

      As you should see, if you don’t already know Haskell, the path is long but I guaranty you it will be very rewarding!

      +

      ps: You can download the source of this yesod blog tutorial at github.com/yogsototh/yosog.

      +
      +
      +
        +
      1. By view I mean yesod widget’s hamlet, lucius and julius files.

      2. +
      +
      ]]>
      +
      + + Increase the power of deficient languages. + + http://yannesposito.com/Scratch/en/blog/SVG-and-m4-fractals/index.html + 2011-10-20T00:00:00Z + 2011-10-20T00:00:00Z + Yesod logo made in SVG and m4

      +
      + +

      tl;dr: How to use m4 to increase the power of deficient languages. Two examples: improve xslt syntax and make fractal with svg.

      +
      + +

      xml was a very nice idea about structuring data. Some people where so enthusiastic about xml they saw it everywhere. The idea was: the future is xml. Then some believed it would be a good idea to invent many xml compatible format and even programming languages with xml syntax.

      +

      Happy! Happy! Joy! Joy!

      +

      Unfortunately, xml was made to transfert structured data. Not a format a human should see or edit directly. The sad reality is xml syntax is simply verbose and ugly. Most of the time it shouldn’t be a problem, as nobody should see it. In a perfect nice world, we should never deal directly with xml but only use software which deal with it for us. Guess what? Our world isn’t perfect. Too sad, a bunch of developer have to deal directly with this ugly xml.

      +

      Unfortunately xml isn’t the only case of misused format I know. You have many format for which it would be very nice to add variables, loops, functions…

      +

      If like me you hate with passion xslt or writing xml, I will show you how you could deal with this bad format or language.

      +

      The xslt Example

      +

      Let’s start by the worst case of misused xml I know: xslt. Any developer who had to deal with xslt know how horrible it is.

      +

      In order to reduce the verbosity of such a bad languages, there is a way. m4. Yes, the preprocessor you use when you program in C and C++.

      +

      Here are some example:

      +
        +
      • Variable, instead of writing the natural myvar = value, here is the xslt way of doing this:
      • +
      +
      <xsl:variable name="myvar" select="value"/>
      +
        +
      • Printing something. Instead of print "Hello world!" here is the xslt equivalent:
      • +
      +
      <xsl:text 
      +    disable-output-escaping="yes"><![CDATA[Hello world!
      +]]></xsl:text>
      +
        +
      • printing the value of a variable, instead of print myvar the xslt is:
      • +
      +
      <xslt:value-of select="myvar"/>
      +
        +
      • Just try to imagine how verbose it is to declare a function with this language.
      • +
      +

      The cure (m4 to the rescue)

      +
      <?xml version="1.0" standalone="yes"?> <!-- YES its <span class="sc">xml</span> -->
      +<!-- ← start a comment, then write some m4 directives:
      +
      +define(`ydef',`<xsl:variable name="$1" select="$2"/>')
      +define(`yprint',`<xsl:text disable-output-escaping="yes"><![CDATA[$1]]></xsl:text>')
      +define(`yshow',`<xsl:value-of select="$1"/>')
      +
      +-->
      +<!-- Yes, <span class="sc">xml</span> sucks to be read -->
      +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      +<!-- And it sucks even more to edit -->
      +<xsl:template match="/">
      +    ydef(myvar,value)
      +    yprint(Hello world!)
      +    yshow(myvar)
      +</xsl:template>
      +

      Now just compile this file:

      +
      m4 myfile.m4 > myfile.xslt
      +

      Profit! Now xslt is more readable and easier to edit!

      +

      The cool part: Fractals!

      +

      svg is an xml format used to represent vector graphics, it even support animations. At its beginning some people believed it would be the new Flash. Apparently, it will be more canvas + js.

      +

      Let me show you the result:

      +

      Yesod logo made in SVG and m4 Click to view directly the svg. It might slow down your computers if you have an old one.

      +

      The positionning of the “esod” text with regards to the reversed “λ” was done by changing position in firebug. I didn’t had to manually regenerate to test.

      +

      Making such a fractal is mostly:

      +
        +
      1. take a root element
      2. +
      3. duplicate and transform it (scaling, translating, rotate)
      4. +
      5. the result is a sub new element.
      6. +
      7. repeat from 2 but by taking the sub new element as new root.
      8. +
      9. Stop when recursion is deep enough.
      10. +
      +

      If I had to do this for each step, I had make a lot of copy/paste in my svg, because the transformation is always the same, but I cannot say, use transformation named “titi”. Then instead of manually copying some xml, I used m4

      +

      and here is the commented code:

      +
      <?xml version="1.0" encoding="UTF-8" standalone="no"?>
      +<!--
      +     M4 Macros
      +define(`YTRANSFORMONE', `scale(.43) translate(-120,-69) rotate(-10)')
      +define(`YTRANSFORMTWO', `scale(.43) translate(-9,-67.5) rotate(10)')
      +define(`YTRANSFORMTHREE', `scale(.43) translate(53,41) rotate(120)')
      +define(`YGENTRANSFORM', `translate(364,274) scale(3)')
      +define(`YTRANSCOMPLETE', `
      +    <g id="level_$1">
      +        <use style="opacity: .8" transform="YTRANSFORMONE" xlink:href="#level_$2" />
      +        <use style="opacity: .8" transform="YTRANSFORMTWO" xlink:href="#level_$2" />
      +        <use style="opacity: .8" transform="YTRANSFORMTHREE" xlink:href="#level_$2" />
      +    </g>
      +    <use transform="YGENTRANSFORM" xlink:href="#level_$1" />
      +')
      + -->
      +<svg 
      +    xmlns="http://www.w3.org/2000/svg" 
      +    xmlns:xlink="http://www.w3.org/1999/xlink"
      +    x="64" y="64" width="512" height="512" viewBox="64 64 512 512"
      +    id="svg2" version="1.1">
      +    <g id="level_0"> <!-- some group, if I want to add other elements -->
      +        <!-- the text "λ" -->
      +        <text id="lambda" 
      +            fill="#333" style="font-family:Ubuntu; font-size: 100px"
      +            transform="rotate(180)">λ</text>
      +    </g>
      +    <!-- the text "esod" -->
      +    <text 
      +        fill="#333" 
      +        style="font-family:Ubuntu; font-size: 28px; letter-spacing: -0.10em" 
      +        x="-17.3" 
      +        y="69" 
      +        transform="YGENTRANSFORM">esod</text>
      +    <!-- ROOT ELEMENT -->
      +    <use transform="YGENTRANSFORM" xlink:href="#level_0" />
      +
      +    YTRANSCOMPLETE(1,0) <!-- First recursion -->
      +    YTRANSCOMPLETE(2,1) <!-- deeper -->
      +    YTRANSCOMPLETE(3,2) <!-- deeper -->
      +    YTRANSCOMPLETE(4,3) <!-- even deeper -->
      +    YTRANSCOMPLETE(5,4) <!-- Five level seems enough -->
      +</svg>
      +

      and I compiled it to svg and then to png with:

      +
      m4 yesodlogo.m4 > yesodlogo.svg && convert yesodlogo.svg yesodlogo.png
      +

      The main λ is duplicated 3 times. Each transformation is named by: YTRANSFORMONE, YTRANSFORMTWO and YTRANSFORMTHREE.

      +

      Each transformation is just a similarity (translate + rotation + scale).

      +

      Once fixed, we should now simply copy and repeat for each new level.

      +

      Now it is time to talk about where the magic occurs: YTRANSCOMPLETE. This macro takes two arguments. The current depth and the preceding one. It duplicates using the three transformations the preceding level.

      +
        +
      • At level 0 there is only one λ,
      • +
      • at level 1 there is 3 λ,
      • +
      • at level 2 there is 9 λ
      • +
      • etc…
      • +
      +

      At the final 5th level there is 35=243 λ. All level combined have 36-1 / 2 = 364 λ.

      +

      I could preview the final result easily. Without the macro system, I would have to make 5 copy/paste + modifications for each try.

      +

      Conclusion

      +

      It was fun to make a fractal in svg, but the interesting part is how to augment the power of a language using this preprocessor method. I used the xslt trick at work for example. I also used it to make include inside obscure format. If all you want is to generate a minimal static website withou using nanoc, jekyll or hakyll (ther are plenty other alternatives). You can consider using m4 to generate your html instead of copy/paste the menu and the footer, or using AJAX.

      +

      Another usage I thouhgt about is to use m4 to organize languages such as brainfuck.

      ]]>
      +
      + + Yesod excellent ideas + + http://yannesposito.com/Scratch/en/blog/Yesod-excellent-ideas/index.html + 2011-10-04T00:00:00Z + 2011-10-04T00:00:00Z + Title image

      +
      + +

      tl;dr:

      +

      Yesod is a framework which has recently matured to the point where you should consider using it. Before telling you why you should learn Haskell and use Yesod, I will illustrate the many features Yesod introduces which are missing in other frameworks.

      +
      + +

      Type safety

      +

      Let’s start by an obligatory link from xkcd:

      +
      +SQL injection by a mom

      SQL injection by a mom

      +
      +

      When you create a web application, a lot of time is spent dealing with strings. Strings for URL, HTML, JavaScript, CSS, SQL, etc… To prevent malicious usage you have to protect each strings to be sure, no script will pass from one point to another. Suppose a user enter this user name:

      +
      Newton<script>alert("An apple fall")</script>
      +

      You must transform each < into &lt;. Without this transformation alert will appear each time you try to display this user name. Safe types associate with each string what kind of string it is. Is it a string for URL? For javascript? For HTML? And the right protection is made by default to prevent problems.

      +

      Yesod does its best to handle cross scripting issues. Both between the client and the server and between the server and your DB. Here is an example:

      +

      Go to the other page ~~~~~~

      +

      As AnotherPageR is of type URL and it could not contains something nefarious. It will be an URL safe. Not something like:

      +
      falselink"><script> bad_code(); </script><a href="pipo
      +

      Widgets

      +

      Yesod’s widgets are different from javascript widget. For yesod, widgets are sets of small parts of a web application. If you want to use many widgets in a same page yesod do the work. Some examples of widget are:

      +
        +
      • the footer of a webpage,
      • +
      • the header of a webpage with a menu,
      • +
      • a button which appears only when scrolling down,
      • +
      • etc…
      • +
      +

      For each of this part, you might need,

      +
        +
      • a bit of HTML,
      • +
      • a bit of CSS and
      • +
      • a bit of javascript.
      • +
      +

      Some in the header, some in the body.

      +

      You can declare a widget as this (note I use a very high meta-language):

      +
      htmlheader = ...
      +cssheader = ...
      +javascriptheader = ...
      +htmlbody = ...
      +

      The real syntax is:

      +
      toWidgetHeader cassiusFile "button.cassius"
      +toWidgetHeader juliusFile "button.julius"
      +toWidget       hamletFile "buttonTemplate.hamlet"
      +

      Note the awesome Shakespearean inspired name convention. Another good reason to use yesod.

      +
        +
      • Cassius & Lucius of CSS (a lot similar to SASS and SCSS),
      • +
      • Julius for JavaScript (note a CoffeeScript is somewhere in the source of yesod),
      • +
      • Hamlet for HTML (similar to haml)
      • +
      +

      And when your page render, yesod makes it easy to render everything nicely:

      +
      myBigWidget =  menuWidget >> contentWidget >> footerWidget
      +

      Furthermore, if you use say 10 widgets each with a bit of CSS, yesod will create a unique and compressed CSS file. Except if you expressed a need to change the header by using different CSS.

      +

      This is just awesome!

      +

      Optimized routing

      +

      In standard routing system you have for each entry a couple: regexp → handler

      +

      The only way to discover the right rules is to match each regexp to the current URL. Then you can see behaviour such as, if you change the order of the rules you can lose or win time.

      +

      On the other hand yesod compiles the routes. Therefore it can optimize it. Of course two routes must not interfere.

      +
      /blog/2003  Date2003R
      +/blog/$DATE DateR
      +

      is invalid by default (you can make it valid, but I don’t think it is a good idea).

      +

      You’d better

      +
      /blog/$DATE DateR
      +

      and test if date = 2003 inside the handler.

      +

      Why yesod?

      +
        +
      1. Speed. This is just astounding. Look at this and then to this.
      2. +
      3. Haskell. This is certainly hard to learn but also incredibly awesome. If you want to make you a favor. Just learn Haskell. It will be difficult, far more than you can imagine. It is very different from all other languages I used. But it will blow your mind and learn you a bunch of new programming concepts.
      4. +
      5. Good ideas, excellent community. I follow yesod from some month now and the speed at which the project progress is incredible.
      6. +
      +

      If you are a haskeller, I believe you shouldn’t fear the special syntax imposed by the standard yesod way of doing things. Just try it more than the firsts basic tutorials.

      +

      Until here I believe it goes in the right direction. Even if I believe the real future is by generating HTML pages from the client (using javascript) and server limited to serve JSON (or XML, or any object representation system).

      +

      To conclude, Yesod is awesome. Just overcome the difficulties about learning a bit of haskell and try it!

      ]]>
      +
      + + Programming Language Experience + + http://yannesposito.com/Scratch/en/blog/programming-language-experience/index.html + 2011-09-28T00:00:00Z + 2011-09-28T00:00:00Z + Title image

      +
      +tl;dr: My short and highly subjective feelings about the programming languages I used. +
      + +

      BASIC

      +

      Title image

      +

      The language of my firsts programs! I was about 10, with an MO5 and Amstrad CPC 6128 and even with my Atari STe. This is the language of GOTOs. Ô nostalgia. Unfortunately this might be the only interesting part of this language.

      +

      Today this language is obsolescent. It is not even good to learn programming. I know some compiler exists now. But this is not enough to try to learn it.

      +
      READY
      +10 PRINT "HELLO WORLD!"
      +20 GOTO 10
      +RUN
      +

      I also remember I copied some game source code from some magazine. Most lines were like:

      +
      3110 DATA FA,01,FF,FF,FF,FF,00,23,22,43,DA,DE,EE,FF,FF,FF,00,03,4A,F2
      +

      What a pleasure!

      + +

      Dragon fractal

      +

      I was about 10 when I played with logo to draw things on a screen.

      +

      I remember the Bach’s music while the program loaded.

      +

      At that time we had to load the program into the memory using tapes. This one was a rare one. It didn’t made an awfull ‘Krrrkrr cssssss krrr’ noise.

      +

      Some years after, I used it to learn programming to my college student. It was a really good first language. Making fractals is like a game for children.

      +

      Here is the code to draw the dragon fractal:

      +
      HIDETURTLE
      +
      +PENUP
      +SETXY -200 0
      +RIGHT 90
      +PENDOWN
      +
      +to dragon :degree :size
      +    setpensize 1
      +    if :size>5  [setpensize 2]
      +    if :size>10 [setpensize 3]
      +    if :size>20 [setpensize 4]
      +    if :size>40 [setpensize 5]
      +    ifelse :degree=0 [
      +        fd :size
      +    ][
      +        left  45 dragon (:degree-1) (size/4)
      +        right 90 dragon (:degree-1) (size/2)
      +        left  90 dragon (:degree-1) (size/4)
      +        right 45
      +    ]
      +end
      +
      +dragon 6 3000
      +

      Pascal

      +

      The eternal second.

      +

      I made my firsts real serious program with Pascal. I must confess I find it inferior to C. I made graph algorithms, sort algorithms even some IA (genetic) algorithms. In the end I prefer C.

      +

      C

      +

      Pointer representation from Dancing links

      +

      The pointer’s language.

      +

      Le programming language.

      +

      Once you understand loops and recursion, it is time to do serious things. If you want to make good quality code, knowing C is almost mandatory.

      +

      This language is close to the machine language. So much, there is (mostly) a linear relation between the size of your code and the size of the compiled one.

      +

      In short, each time you write a C instruction there won’t be anything strange that will occurs, like starting a long algorithm behind the scene.

      +

      While close to the machine, C keep sufficient abstractions to be fun.

      +

      I made a lot of program with it. From sort algorithms to AI ones (SAT3), system, network programming, etc… It is a very useful language that will help you understand how things works on your computer. Most modern computer language hide a lot of informations on what occurs. This is not the case with C.

      +

      ADA

      +

      The “super clean” one.

      +

      I liked ADA. I must confess I didn’t used it a lot. May be one day I will try it again. I was impressed by asynchronous programming with it. What you need to know is this old language had certainly inspired most new object oriented languages.

      +

      Object Oriented Languages

      +

      Until here I just described imperative languages without any object notion.

      +

      More clearly, the language didn’t helped you to structure your program.

      +

      In order to limit the number of bugs, particularly for huge programs, we started to think about how to organize computer programs. In the end, from the imperative language culture, it produced the Object Oriented programming (OOP). Beware, the Object Oriented programming isn’t a miracle. Proof? How many bug free software do you use? Furthermore, OOP doesn’t fit all problems. But to make a bank application, an application which help to manage stock, clients or text archives or more generally an information system, the OOP is not so bad.

      +

      Then Object Oriented Languages appeared everywhere.

      +

      C++

      +

      Messy router

      +

      The ugly

      +

      Industry wanted an Object Oriented Language without losing all their old C code. Solution, keep C and add an Object layer on it. My main concern about C++ is: it does too many things. I appreciated multiple inheritance and templates. In reality I liked a lot C++ while I was working alone. I used it to write DEES my main thesis software. My only concern was about a lack in the STL. In the doc, one could use String<T>. But in reality, T had to be only char or char16. Then I had to reduce my alphabet to 216 letters. Except for some application, the alphabet must be far larger than that. en: To conclude, I’d say, C++ is very good if you work alone or with a fixed subset of its features.

      +

      fr:

      +

      Eiffel

      +

      Eiffel tower construction

      +

      Yes, it is a really nice language. Full object in mind. Far cleaner than C++. But it isn’t so popular. Behind C++ there is a large community to help new users and to write libraries. Furthermore, I preferred working with C++. When I learned Eiffel, I programmed a lot with C and liked its syntax.

      +

      Java

      +

      Holy Grail from the Monty Python

      +

      The first time I heard about Java it was le Grail!

      +

      Perfect portability, your program will work on all platform. The language helps limit mistakes, and force you to use good programming habits. But…

      +

      But It is extremely verbose. Many limitations are quite boring if you know what you’re doing.

      +

      For example, there is no multiple inheritance. Generally it is a coherent choice when there are a way to compensate. In Java, there are interfaces for this. Except, interfaces can only add methods to a class. You cannot add any attribute to a class except by subclassing. I really lacked this feature.

      +

      I made a GUI using Java Swing and I created my own notification system between different element of the GUI. In the beginning I only needed to send notification one to one. After some time, I wanted to send one to many notifications. I had to make a bunch of copy/paste inside all my subclasses! Copy/paste are exactly what should be avoided the most by object oriented languages.

      +

      Another thing: threads. I was forced to make my own thread management system to avoid locks and send notifications between threads (wait the end of this thread, …). At that time I used Java 1.5. This problem should have been solved with Java 1.6. I wish it is the case, but lacking such an essential feature for a language was very bad.

      +

      In the same idea, it was very long to wait for the “foreach” loops.

      +

      After my experience, I don’t recommend Java. Portability does not worth this price.

      +

      GUI portability means mediocre experience on all platforms. Any system it might be (wxWidget, QT, etc…).

      +

      The Java ideology is “closed”. But it resolve a big problem. It helps medium to low quality developers to work in team without the ability to make too much harm to the product. A good programmer will be able to make very interesting things with it thought. Please note I didn’t say Java programmer are bad programmer.

      +

      Objective-C

      +

      Xcode Logo

      +

      The language I learned and used only to make application on Apple(c) platform. I learned Objective-C just after Python. It was hard to do it. At first I didn’t liked the syntax and many other details. But it is this kind of language the more you use, the more you like. In fact, Objective-C is a simple language, but associated with the Cocoa framework it is a really good tool. Cocoa is very different to other framework I used before. I find many of its idea extremely good. Both simple and efficient. It might seems like small details on paper, but once you start using it, it makes all the difference.

      +

      Even if Objective-C is a relatively low level language. Its dynamic typing ability make it very good for GUI programming. I recommend to continue working with this language. In the end you’ll certainly find it better than expected.

      +

      Modern Scripting Languages

      +

      PHP

      +

      A Jacky Touch Car

      +

      This small script language that we used all to make our website in the time of animated gifs.

      +

      Nice but no more. Apparently there were a lot of progress since PHP5. Maybe one day I’ll use it again. But behind it, this language has a “script kiddies only” reputation. Also long history of easy to make security holes.

      +

      In reality PHP is just behind C for the abstraction level. Therefore it has a lot of organisation problems and make it easier to create bugs. For web applications it is a real problem.

      +

      PHP remains for me the SQL injection language. I made a bit of PHP not so long ago, and it was a pain to protect my application to SQL injection. Yep, I didn’t found any standard library to make this, but I didn’t searched a lot.

      +

      Python

      +

      Python. Do you speak it?

      +

      Revelation!

      +

      When you were used to work with compiled languages (C++, Java) and you start learning Python, it’s like a punch in the face. Programming like it always should have been. Everything is natural, it’s magic. Yes, as good as this. But something so good must have some drawback.

      +

      And yes, like all interpreted languages, Python is slow. Beware, no just a bit slow like 2 or 3 times slower than C. (like Java for example). No, really slow, about 10 to 20 times slower than C. Argh… Note it is completely usable for many things.

      +

      Awk

      +

      If you have to “filter” some files and the filter is not too complicated awk is the ideal language to do this. For example, if you want to know which words in a text file are most used. I used it to modify hundreds of XML files in an easier manner than XSLT.

      +

      Perl

      +

      Perl is magic, but the syntax is so hideous nobody can like to work in an environment with many different person in Perl. Or at least, all other collaborators must be excellent programmers. A great feature of perl is its integration with regular expression in its syntax:

      +
      $var =~ s/toto/titi/g
      +

      This program will replace every toto by titi inside the $var variable. The Perl code is often very compact and generally unreadable. But it is a language good to know. It is a kind of awk under steroids.

      +

      Ruby

      +

      Ruby is a very good language. It is often compared (opposed ?) to Python. There are the regular expression operators Perl inside the langage. But the syntax is extremely clear, like in Python. Many feature were inspired by functional programming (as in Python). I used it a lot. It is the worst language I know in term of efficiency. This is the language that lose almost all benchmarks. But it is the perfect tool for prototypes. If you want to make a website prototype, RoR (Ruby on Rails) is certainly one of the best system known to mankind. From idea to realisation, few time will occur. Make this site work for thousands of people, will, on the other hand, certainly require a lot of optimisations.

      +

      One of the greatest Ruby feature is its ability to make the program extremely readable. It is very close to natural language. On the other hand, I found the Object Oriented layer a bit disappointing. The fact there is no real “class variable” but only “tree class variable” for example.

      +

      Considering the community, the ruby one feels closer to the creative than the engineer. I am under the impression designer tends to use Ruby instead of Python.

      +

      Javascript

      +

      It is the good surprise. During years, javascript was considered as an annoying web experience language. In reality, javascript has many really good qualities. Particularly, it is easy to pass a function in parameter and to create anonymous functions (closures). Recently, javascript became far faster than before and many frameworks and libraries appears:

      +
        +
      • Cappuccino, Objective-J (as in objective-C but with javascript)
      • +
      • Sproutcore
      • +
      • Spine.js
      • +
      • Backbone.js
      • +
      • jQuery
      • +
      • prototype.js
      • +
      +

      Particularly with jQuery we can chain functions. It is very nice to use. As I said, this is a good surprise. Javascript was chosen by chance as the script inside your navigator. Instead of the java inspired syntax, everything else is very good. In order to compensate the syntax, you can use CoffeeScript.

      +

      Functional Languages

      +

      CamL

      +

      I learned CamL during the college. It was really interesting. Functional programming is very different to imperative programming (most of popular languages). I had good mathematic intuitions to use this language. But I must confess I never used it for something serious.

      +

      Haskell

      +

      I believe I will still learning this language in many years. I must say it is a pleasure. Generally it takes me no more than some hours to some days to learn a new programming language. Concerning Haskell, this is very different. To master Haskell you need to understand very abstract concepts. Monads and Arrows are some of them. I didn’t understood them before I read some scientific paper. Many week will be necessary to master it perfectly (if someone does). Also the community is very friendly and nice. There is no “LOL! URAN00B! RTFM!” And no concession has been made to make this language more popular (I’m looking at you C++, Java and Javascript). This langage remain pure (I know there are two meaning).

      +

      Concerning making real product with Haskell. In fact, Haskell is very efficient concerning code change. Refactoring code is incredibly easy with Haskell. And in the same time, the Haskell type system helps to make your product bug free.

      +

      Technically this language is close to perfection. But it has some major problems:

      +
        +
      • not so popular
      • +
      • hard to learn
      • +
      • I also believe there is not actually enough success stories with Haskell
      • +
      +

      On the other hand, knowing Haskell will help you learn a lot of thing about programming in general. You should at least take a look. I made an Haskell introduction if you are curious.

      +

      Unpopular Languages

      +

      Metapost

      +

      Metapost is a language to program drawings. What make metapost very good? It contains a linear solver. This is really useful to draw things. For example if you write:

      +
      AA=1/3[A,B]
      +

      It will place the point AA between the point A and B. More precisely at the barycenter (2xA + B)/3.

      +
      X=whatever[A,B]
      +X=whatever[C,D]
      +

      This second example, will place the point X at the intersection of the two segments AB and CD.

      +

      This feature is very helpful, and not only to draw things. Most programming language should think about adding it.

      +

      zsh

      +

      Yes, zsh is a shell. But it is also a script language very well suited to file management. For now, it is the best shell I used. I prefer zsh to bash.

      +

      Prolog

      +

      I never made something serious with Prolog, but I really loved to use and learn it. I had the chance to learn Prolog with Alain Colmerauer himself. This language try to resolve constraints as much as it can. It has a magic feeling when you use it. We only write constraints, we never put order. A bit like functional programming but far more powerful.

      +

      Languages to discover

      +

      Many languages and framework remains to be learnt and tried. Actually I believe I will stay a while with Haskell. Maybe tomorrow I will look at LISP, Scala or Erlang. I also certainly look at clojure to make web application.

      +

      Tell me if you have any other experience with these programming languages. Of course, my feelings are highly subjectives. But I used all of these languages.

      +

      [STL]: Standard Tempate Library[GUI]: Graphic User Interface

      ]]>
      +
      + + Higher order function in zsh + + http://yannesposito.com/Scratch/en/blog/Higher-order-function-in-zsh/index.html + 2011-09-28T00:00:00Z + 2011-09-28T00:00:00Z + Title image

      +
      + +

      UPDATE: Nicholas Sterling had discovered a way to implement anonymous functions Thanks!

      +

      With this last version you should use map if you use external function. mapl to use lambda function. And mapa for arithmetic operations.

      +

      Example:

      +
      $ filterl 'echo $1|grep a >/dev/null' ab cd ef ada
      +ab
      +ada
      +
      +$ folda '$1+$2' {1..5}
      +15
      +
      +$ folda '$1*$2' {1..20}
      +2432902008176640000
      +
      +$ mapl 'echo X $1:t Y' ~/.zsh/functional/src/*
      +X each Y
      +X filter Y
      +X fold Y
      +X map Y
      +
      +$ mapa '$1*2' {1..3}
      +2
      +4
      +6
      +
      +$ mapl 'echo result $1' $(mapa '$1+5' $(mapa '$1*2' {1..3}))
      +result 7
      +result 9
      +result 11
      +
      +

      tl;dr: some simple implementation of higher order function for zsh.

      +
      + +

      Why is it important to have these functions? Simply because, the more I programmed with zsh the more I tended to work using functional programming style.

      +

      The minimal to have better code are the functions map, filter and fold.

      +

      Let’s compare. First a program which convert all gif to png in many different directories of different projects.

      +

      Before ⇒

      +
      # for each directory in projects dir
      +for toProject in /path/to/projects/*(/N); do
      +    # toProject is /path/to/projects/foo
      +    # project become foo (:t for tail)
      +    project=${toProject:t}
      +    for toResource in $toProject/resources/*.gif(.N); do
      +        convert $toResource ${toResource:r}.png && \
      +        \rm -f $toResource
      +    done
      +done
      +
        +
      • The (/N) means to select only directory and not to crash if there isn’t any.
      • +
      • The (.N) means to select only files and not to crash if there isn’t any.
      • +
      • The :t means tail; if toto=/path/to/file.ext then ${toto:t}=file.ext.
      • +
      +

      After ⇒

      +
      gif_to_png() { convert $1 ${1:r}.png && \rm -f $1 }
      +
      +handle_resources() { map gif_to_png $1/resources/*.gif(.N) }
      +
      +map handle_resources /path/to/projects/*(/N)
      +

      No more bloc! It might be a little bit harder to read if you’re not used to functional programming notation. But it is more concise and robusts.

      +

      Another example with some tests.

      +

      Find all files in project not containing an s which their name contains their project name:

      +

      Before ⇒

      +
      for toProject in Projects/*; do
      +    project=$toProject:t
      +    if print -- project | grep -v s >/dev/null
      +    then
      +        print $project
      +        for toResource in $toProject/*(.N); do
      +            if print -- ${toResource:t} | grep $project >/dev/null; then
      +                print -- "X $toResource"
      +            fi
      +        done
      +    fi
      +done
      +

      After ⇒

      +
      contain_no_s() { print $1 | grep -v s }
      +
      +function verify_file_name {                               
      +    local project=$1:t
      +    contains_project_name() { print $1:t | grep $project }
      +    map "print -- X" $(filter contains_project_name $1/*(.N))
      +}
      +
      +map verify_file_name $( filter contain_no_s Projects/* )
      +

      Also, the first verstion is a bit easier to read. But the second one is clearly far superior in architecture. I don’t want to argue why here. Just believe me that the functional programming approach is superior.

      +

      You can find an updated version of the code (thanks to Arash Rouhani). An older version is here thought. Here is the (first version) source code:

      +
      #!/usr/bin/env zsh
      +
      +# Provide higer-order functions 
      +
      +# usage:
      +#
      +# $ foo(){print "x: $1"}
      +# $ map foo a b c d
      +# x: a
      +# x: b
      +# x: c
      +# x: d
      +function map {
      +    local func_name=$1
      +    shift
      +    for elem in $@; print -- $(eval $func_name $elem)
      +}
      +
      +# $ bar() { print $(($1 + $2)) }
      +# $ fold bar 0 1 2 3 4 5
      +# 15
      +# -- but also
      +# $ fold bar 0 $( seq 1 100 )
      +function fold {
      +    if (($#<2)) {
      +        print -- "ERROR fold use at least 2 arguments" >&2
      +        return 1
      +    }
      +    if (($#<3)) {
      +        print -- $2
      +        return 0
      +    } else {
      +        local acc
      +        local right
      +        local func_name=$1
      +        local init_value=$2
      +        local first_value=$3
      +        shift 3
      +        right=$( fold $func_name $init_value $@ )
      +        acc=$( eval "$func_name $first_value $right" )
      +        print -- $acc
      +        return 0
      +    }
      +}
      +
      +# usage:
      +#
      +# $ baz() { print $1 | grep baz }
      +# $ filter baz titi bazaar biz
      +# bazaar
      +function filter {
      +    local predicate=$1
      +    local result
      +    typeset -a result
      +    shift
      +    for elem in $@; do
      +        if eval $predicate $elem >/dev/null; then
      +            result=( $result $elem )
      +        fi
      +    done
      +    print $result
      +}
      ]]>
      +
      + + Learn Vim Progressively + + http://yannesposito.com/Scratch/en/blog/Learn-Vim-Progressively/index.html + 2011-08-25T00:00:00Z + 2011-08-25T00:00:00Z + Über leet use vim!

      +
      + +

      tl;dr: You want to teach yourself vim (the best text editor known to human kind) in the fastest way possible. This is my way of doing it. You start by learning the minimal to survive, then you integrate all the tricks slowly.

      +
      + +

      Vim the Six Billion Dollar editor

      +
      +

      Better, Stronger, Faster.

      +
      +

      Learn vim and it will be your last text editor. There isn’t any better text editor that I know of. It is hard to learn, but incredible to use.

      +

      I suggest you teach yourself Vim in 4 steps:

      +
        +
      1. Survive
      2. +
      3. Feel comfortable
      4. +
      5. Feel Better, Stronger, Faster
      6. +
      7. Use superpowers of vim
      8. +
      +

      By the end of this journey, you’ll become a vim superstar.

      +

      But before we start, just a warning. Learning vim will be painful at first. It will take time. It will be a lot like playing a musical instrument. Don’t expect to be more efficient with vim than with another editor in less than 3 days. In fact it will certainly take 2 weeks instead of 3 days.

      +

      1st Level – Survive

      +
        +
      1. Install vim
      2. +
      3. Launch vim
      4. +
      5. DO NOTHING! Read.
      6. +
      +

      In a standard editor, typing on the keyboard is enough to write something and see it on the screen. Not this time. Vim is in Normal mode. Let’s go to Insert mode. Type the letter i.

      +

      You should feel a bit better. You can type letters like in a standard editor. To get back to Normal mode just press the ESC key.

      +

      You now know how to switch between Insert and Normal mode. And now, here are the commands that you need in order to survive in Normal mode:

      +
      +
        +
      • iInsert mode. Type ESC to return to Normal mode.
      • +
      • x → Delete the char under the cursor
      • +
      • :wq → Save and Quit (:w save, :q quit)
      • +
      • dd → Delete (and copy) the current line
      • +
      • p → Paste
      • +
      +

      Recommended:

      +
        +
      • hjkl (highly recommended but not mandatory) → basic cursor move (←↓↑→). Hint: j looks like a down arrow.
      • +
      • :help <command> → Show help about <command>. You can use :help without a <command> to get general help.
      • +
      +
      +

      Only 5 commands. That is all you need to get started. Once these command start to become natural (maybe after a day or so), you should move on to level 2.

      +

      But first, just a little remark about Normal mode. In standard editors, to copy you have to use the Ctrl key (Ctrl-c generally). In fact, when you press Ctrl, it is as if all of your keys change meaning. Using vim in normal mode is a bit like having the editor automatically press the Ctrl key for you.

      +

      A last word about notations:

      +
        +
      • instead of writing Ctrl-λ, I’ll write <C-λ>.
      • +
      • commands starting with : end with <enter>. For example, when I write :q, I mean :q<enter>.
      • +
      +

      2nd Level – Feel comfortable

      +

      You know the commands required for survival. It’s time to learn a few more commands. These are my suggestions:

      +
        +
      1. Insert mode variations:

        +
        +
          +
        • a → insert after the cursor
        • +
        • o → insert a new line after the current one
        • +
        • O → insert a new line before the current one
        • +
        • cw → replace from the cursor to the end of the word
        • +
        +
      2. +
      3. Basic moves

        +
        +
          +
        • 0 → go to the first column
        • +
        • ^ → go to the first non-blank character of the line
        • +
        • $ → go to the end of line
        • +
        • g_ → go to the last non-blank character of line
        • +
        • /pattern → search for pattern
        • +
        +
      4. +
      5. Copy/Paste

        +
        +
          +
        • P → paste before, remember p is paste after current position.
        • +
        • yy → copy the current line, easier but equivalent to ddP
        • +
        +
      6. +
      7. Undo/Redo

        +
        +
          +
        • u → undo
        • +
        • <C-r> → redo
        • +
        +
      8. +
      9. Load/Save/Quit/Change File (Buffer)

        +
        +
          +
        • :e <path/to/file> → open
        • +
        • :w → save
        • +
        • :saveas <path/to/file> → save to <path/to/file>
        • +
        • :x, ZZ or :wq → save and quit (:x only save if necessary)
        • +
        • :q! → quit without saving, also: :qa! to quit even if there are modified hidden buffers.
        • +
        • :bn (resp. :bp) → show next (resp. previous) file (buffer)
        • +
        +
      10. +
      +

      Take the time to learn all of these command. Once done, you should be able to do every thing you are able to do in other editors. You may still feel a bit awkward. But follow me to the next level and you’ll see why vim is worth the extra work.

      +

      3rd Level – Better. Stronger. Faster.

      +

      Congratulation for reaching this far! Now we can start with the interesting stuff. At level 3, we’ll only talk about commands which are compatible with the old vi editor.

      +

      Better

      +

      Let’s look at how vim could help you to repeat yourself:

      +
        +
      1. . → (dot) will repeat the last command,
      2. +
      3. N<command> → will repeat the command N times.
      4. +
      +

      Some examples, open a file and type:

      +
      +
        +
      • 2dd → will delete 2 lines
      • +
      • 3p → will paste the text 3 times
      • +
      • 100idesu [ESC] → will write “desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu”
      • +
      • . → Just after the last command will write again the 100 “desu”.
      • +
      • 3. → Will write 3 “desu” (and not 300, how clever).
      • +
      +
      +

      Stronger

      +

      Knowing how to move efficiently with vim is very important. Don’t skip this section.

      +
        +
      1. NG → Go to line N
      2. +
      3. gg → shortcut for 1G - go to the start of the file
      4. +
      5. G → Go to last line
      6. +
      7. Word moves:

        +
        +
          +
        1. w → go to the start of the following word,
        2. +
        3. e → go to the end of this word.
        4. +
        +

        By default, words are composed of letters and the underscore character. Let’s call a WORD a group of letter separated by blank characters. If you want to consider WORDS, then just use uppercase characters:

        +
          +
        1. W → go to the start of the following WORD,
        2. +
        3. E → go to the end of this WORD.
        4. +
        +

        Word moves example

        +
      8. +
      +

      Now let’s talk about very efficient moves:

      +
      +
        +
      • % : Go to the corresponding (, {, [.
      • +
      • * (resp. #) : go to next (resp. previous) occurrence of the word under the cursor
      • +
      +
      +

      Believe me, the last three commands are gold.

      +

      Faster

      +

      Remember about the importance of vi moves? Here is the reason. Most commands can be used using the following general format:

      +

      <start position><command><end position>

      +

      For example : 0y$ means

      +
        +
      • 0 → go to the beginning of this line
      • +
      • y → yank from here
      • +
      • $ → up to the end of this line
      • +
      +

      We also can do things like ye, yank from here to the end of the word. But also y2/foo yank up to the second occurrence of “foo”.

      +

      But what was true for y (yank), is also true for d (delete), v (visual select), gU (uppercase), gu (lowercase), etc…

      +

      4th Level – Vim Superpowers

      +

      With all preceding commands you should be comfortable using vim. But now, here are the killer features. Some of these features were the reason I started to use vim.

      +

      Move on current line: 0 ^ $ g_ f F t T , ;

      +
      +
        +
      • 0 → go to column 0
      • +
      • ^ → go to first character on the line
      • +
      • $ → go to the last column
      • +
      • g_ → go to the last character on the line
      • +
      • fa → go to next occurrence of the letter a on the line. , (resp. ;) will find the next (resp. previous) occurrence.
      • +
      • t, → go to just before the character ,.
      • +
      • 3fa → find the 3rd occurrence of a on this line.
      • +
      • F and T → like f and t but backward. Line moves
      • +
      +
      +

      A useful tip is: dt" → remove everything until the ".

      +

      Zone selection <action>a<object> or <action>i<object>

      +

      These command can only be used after an operator in visual mode. But they are very powerful. Their main pattern is:

      +

      <action>a<object> and <action>i<object>

      +

      Where action can be any action, for example, d (delete), y (yank), v (select in visual mode). The object can be: w a word, W a WORD (extended word), s a sentence, p a paragraph. But also, natural character such as ", ', ), }, ].

      +

      Suppose the cursor is on the first o of (map (+) ("foo")).

      +
      +
        +
      • vi" → will select foo.
      • +
      • va" → will select "foo".
      • +
      • vi) → will select "foo".
      • +
      • va) → will select ("foo").
      • +
      • v2i) → will select map (+) ("foo")
      • +
      • v2a) → will select (map (+) ("foo"))
      • +
      +
      +

      Text objects selection

      +

      Select rectangular blocks: <C-v>.

      +

      Rectangular blocks are very useful for commenting many lines of code. Typically: 0<C-v><C-d>I-- [ESC]

      +
        +
      • ^ → go to the first non-blank character of the line
      • +
      • <C-v> → Start block selection
      • +
      • <C-d> → move down (could also be jjj or %, etc…)
      • +
      • I-- [ESC] → write -- to comment each line
      • +
      +

      Rectangular blocks

      +

      Note: in Windows you might have to use <C-q> instead of <C-v> if your clipboard is not empty.

      +

      Completion: <C-n> and <C-p>.

      +

      In Insert mode, just type the start of a word, then type <C-p>, magic… Completion

      +

      Macros : qa do something q, @a, @@

      +

      qa record your actions in the register a. Then @a will replay the macro saved into the register a as if you typed it. @@ is a shortcut to replay the last executed macro.

      +
      +

      Example

      +

      On a line containing only the number 1, type this:

      +
        +
      • qaYp<C-a>q

      • +
      • qa start recording.
      • +
      • Yp duplicate this line.
      • +
      • <C-a> increment the number.
      • +
      • q stop recording.

      • +
      • @a → write 2 under the 1
      • +
      • @@ → write 3 under the 2
      • +
      • Now do 100@@ will create a list of increasing numbers until 103.

      • +
      +
      +

      Macros

      +

      Visual selection: v,V,<C-v>

      +

      We saw an example with <C-v>. There is also v and V. Once the selection has been made, you can:

      +
        +
      • J → join all the lines together.
      • +
      • < (resp. >) → indent to the left (resp. to the right).
      • +
      • = → auto indent
      • +
      +

      Autoindent

      +

      Add something at the end of all visually selected lines:

      +
        +
      • <C-v>
      • +
      • go to desired line (jjj or <C-d> or /pattern or % etc…)
      • +
      • $ go to the end of the line
      • +
      • A, write text, ESC.
      • +
      +

      Append to many lines

      +

      Splits: :split and vsplit.

      +

      These are the most important commands, but you should look at :help split.

      +
      +
        +
      • :split → create a split (:vsplit create a vertical split)
      • +
      • <C-w><dir> : where dir is any of hjkl or ←↓↑→ to change the split.
      • +
      • <C-w>_ (resp. <C-w>|) : maximise the size of the split (resp. vertical split)
      • +
      • <C-w>+ (resp. <C-w>-) : Grow (resp. shrink) split
      • +
      +
      +

      Split

      +

      Conclusion

      +

      That was 90% of the commands I use every day. I suggest that you learn no more than one or two new commands per day. After two to three weeks you’ll start to feel the power of vim in your hands.

      +

      Learning Vim is more a matter of training than plain memorization. Fortunately vim comes with some very good tools and excellent documentation. Run vimtutor until you are familiar with most basic commands. Also, you should read this page carefully: :help usr_02.txt.

      +

      Then, you will learn about !, folds, registers, plugins and many other features. Learn vim like you’d learn piano and all should be fine.

      + + +]]>
      +
      + +
      diff --git a/Scratch/en/blog/index.html b/Scratch/en/blog/index.html new file mode 100644 index 0000000..58ca32e --- /dev/null +++ b/Scratch/en/blog/index.html @@ -0,0 +1,346 @@ + + + + + + YBlog - Blog + + + + + + + + + + + + +
      + + +
      +

      Blog

      +
      +
      +
      +
      + + + + + + + +
      + +

      Archive

      + + + +
      + +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/en/blog/mvc/index.html b/Scratch/en/blog/mvc/index.html new file mode 100644 index 0000000..3bf013e --- /dev/null +++ b/Scratch/en/blog/mvc/index.html @@ -0,0 +1,137 @@ + + + + + + YBlog - MVC explained + + + + + + + + + + + + +
      + + +
      +

      MVC explained

      +
      +
      +
      +
      +

      Why This article and for whom?

      +

      Many website explaining how MVC works. But I can’t found one who explain why.

      +

      I have difficulties to obey some principle don’t know why I should use it. And something better than the:

      +
      +

      “Smarter people than you decided you have to do so”

      +
      +

      This article is for people who like me want to understand the real motivation of using an MVC architecture and which advantage it gives you.

      +

      Let’s start making a project from scratch pretending we don’t know anything about the MVC architecture. Then let see how we’ll naturally derive to an MVC architecture.

      +
      +

      +<%= lnkto(‘→ Next’,‘/blog/mvc/mvc1’) %> +

      +
      + + +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-07-06 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/en/blog/programming-language-experience/index.html b/Scratch/en/blog/programming-language-experience/index.html new file mode 100644 index 0000000..3c7296a --- /dev/null +++ b/Scratch/en/blog/programming-language-experience/index.html @@ -0,0 +1,277 @@ + + + + + + YBlog - Programming Language Experience + + + + + + + + + + + + + +
      + + +
      +

      Programming Language Experience

      +
      +
      +
      +
      +

      Title image

      +
      +tl;dr: My short and highly subjective feelings about the programming languages I used. +
      + +

      BASIC

      +

      Title image

      +

      The language of my firsts programs! I was about 10, with an MO5 and Amstrad CPC 6128 and even with my Atari STe. This is the language of GOTOs. Ô nostalgia. Unfortunately this might be the only interesting part of this language.

      +

      Today this language is obsolescent. It is not even good to learn programming. I know some compiler exists now. But this is not enough to try to learn it.

      +
      READY
      +10 PRINT "HELLO WORLD!"
      +20 GOTO 10
      +RUN
      +

      I also remember I copied some game source code from some magazine. Most lines were like:

      +
      3110 DATA FA,01,FF,FF,FF,FF,00,23,22,43,DA,DE,EE,FF,FF,FF,00,03,4A,F2
      +

      What a pleasure!

      + +

      Dragon fractal

      +

      I was about 10 when I played with logo to draw things on a screen.

      +

      I remember the Bach’s music while the program loaded.

      +

      At that time we had to load the program into the memory using tapes. This one was a rare one. It didn’t made an awfull ‘Krrrkrr cssssss krrr’ noise.

      +

      Some years after, I used it to learn programming to my college student. It was a really good first language. Making fractals is like a game for children.

      +

      Here is the code to draw the dragon fractal:

      +
      HIDETURTLE
      +
      +PENUP
      +SETXY -200 0
      +RIGHT 90
      +PENDOWN
      +
      +to dragon :degree :size
      +    setpensize 1
      +    if :size>5  [setpensize 2]
      +    if :size>10 [setpensize 3]
      +    if :size>20 [setpensize 4]
      +    if :size>40 [setpensize 5]
      +    ifelse :degree=0 [
      +        fd :size
      +    ][
      +        left  45 dragon (:degree-1) (size/4)
      +        right 90 dragon (:degree-1) (size/2)
      +        left  90 dragon (:degree-1) (size/4)
      +        right 45
      +    ]
      +end
      +
      +dragon 6 3000
      +

      Pascal

      +

      The eternal second.

      +

      I made my firsts real serious program with Pascal. I must confess I find it inferior to C. I made graph algorithms, sort algorithms even some IA (genetic) algorithms. In the end I prefer C.

      +

      C

      +

      Pointer representation from Dancing links

      +

      The pointer’s language.

      +

      Le programming language.

      +

      Once you understand loops and recursion, it is time to do serious things. If you want to make good quality code, knowing C is almost mandatory.

      +

      This language is close to the machine language. So much, there is (mostly) a linear relation between the size of your code and the size of the compiled one.

      +

      In short, each time you write a C instruction there won’t be anything strange that will occurs, like starting a long algorithm behind the scene.

      +

      While close to the machine, C keep sufficient abstractions to be fun.

      +

      I made a lot of program with it. From sort algorithms to AI ones (SAT3), system, network programming, etc… It is a very useful language that will help you understand how things works on your computer. Most modern computer language hide a lot of informations on what occurs. This is not the case with C.

      +

      ADA

      +

      The “super clean” one.

      +

      I liked ADA. I must confess I didn’t used it a lot. May be one day I will try it again. I was impressed by asynchronous programming with it. What you need to know is this old language had certainly inspired most new object oriented languages.

      +

      Object Oriented Languages

      +

      Until here I just described imperative languages without any object notion.

      +

      More clearly, the language didn’t helped you to structure your program.

      +

      In order to limit the number of bugs, particularly for huge programs, we started to think about how to organize computer programs. In the end, from the imperative language culture, it produced the Object Oriented programming (OOP). Beware, the Object Oriented programming isn’t a miracle. Proof? How many bug free software do you use? Furthermore, OOP doesn’t fit all problems. But to make a bank application, an application which help to manage stock, clients or text archives or more generally an information system, the OOP is not so bad.

      +

      Then Object Oriented Languages appeared everywhere.

      +

      C++

      +

      Messy router

      +

      The ugly

      +

      Industry wanted an Object Oriented Language without losing all their old C code. Solution, keep C and add an Object layer on it. My main concern about C++ is: it does too many things. I appreciated multiple inheritance and templates. In reality I liked a lot C++ while I was working alone. I used it to write DEES my main thesis software. My only concern was about a lack in the STL. In the doc, one could use String<T>. But in reality, T had to be only char or char16. Then I had to reduce my alphabet to 216 letters. Except for some application, the alphabet must be far larger than that. en: To conclude, I’d say, C++ is very good if you work alone or with a fixed subset of its features.

      +

      fr:

      +

      Eiffel

      +

      Eiffel tower construction

      +

      Yes, it is a really nice language. Full object in mind. Far cleaner than C++. But it isn’t so popular. Behind C++ there is a large community to help new users and to write libraries. Furthermore, I preferred working with C++. When I learned Eiffel, I programmed a lot with C and liked its syntax.

      +

      Java

      +

      Holy Grail from the Monty Python

      +

      The first time I heard about Java it was le Grail!

      +

      Perfect portability, your program will work on all platform. The language helps limit mistakes, and force you to use good programming habits. But…

      +

      But It is extremely verbose. Many limitations are quite boring if you know what you’re doing.

      +

      For example, there is no multiple inheritance. Generally it is a coherent choice when there are a way to compensate. In Java, there are interfaces for this. Except, interfaces can only add methods to a class. You cannot add any attribute to a class except by subclassing. I really lacked this feature.

      +

      I made a GUI using Java Swing and I created my own notification system between different element of the GUI. In the beginning I only needed to send notification one to one. After some time, I wanted to send one to many notifications. I had to make a bunch of copy/paste inside all my subclasses! Copy/paste are exactly what should be avoided the most by object oriented languages.

      +

      Another thing: threads. I was forced to make my own thread management system to avoid locks and send notifications between threads (wait the end of this thread, …). At that time I used Java 1.5. This problem should have been solved with Java 1.6. I wish it is the case, but lacking such an essential feature for a language was very bad.

      +

      In the same idea, it was very long to wait for the “foreach” loops.

      +

      After my experience, I don’t recommend Java. Portability does not worth this price.

      +

      GUI portability means mediocre experience on all platforms. Any system it might be (wxWidget, QT, etc…).

      +

      The Java ideology is “closed”. But it resolve a big problem. It helps medium to low quality developers to work in team without the ability to make too much harm to the product. A good programmer will be able to make very interesting things with it thought. Please note I didn’t say Java programmer are bad programmer.

      +

      Objective-C

      +

      Xcode Logo

      +

      The language I learned and used only to make application on Apple(c) platform. I learned Objective-C just after Python. It was hard to do it. At first I didn’t liked the syntax and many other details. But it is this kind of language the more you use, the more you like. In fact, Objective-C is a simple language, but associated with the Cocoa framework it is a really good tool. Cocoa is very different to other framework I used before. I find many of its idea extremely good. Both simple and efficient. It might seems like small details on paper, but once you start using it, it makes all the difference.

      +

      Even if Objective-C is a relatively low level language. Its dynamic typing ability make it very good for GUI programming. I recommend to continue working with this language. In the end you’ll certainly find it better than expected.

      +

      Modern Scripting Languages

      +

      PHP

      +

      A Jacky Touch Car

      +

      This small script language that we used all to make our website in the time of animated gifs.

      +

      Nice but no more. Apparently there were a lot of progress since PHP5. Maybe one day I’ll use it again. But behind it, this language has a “script kiddies only” reputation. Also long history of easy to make security holes.

      +

      In reality PHP is just behind C for the abstraction level. Therefore it has a lot of organisation problems and make it easier to create bugs. For web applications it is a real problem.

      +

      PHP remains for me the SQL injection language. I made a bit of PHP not so long ago, and it was a pain to protect my application to SQL injection. Yep, I didn’t found any standard library to make this, but I didn’t searched a lot.

      +

      Python

      +

      Python. Do you speak it?

      +

      Revelation!

      +

      When you were used to work with compiled languages (C++, Java) and you start learning Python, it’s like a punch in the face. Programming like it always should have been. Everything is natural, it’s magic. Yes, as good as this. But something so good must have some drawback.

      +

      And yes, like all interpreted languages, Python is slow. Beware, no just a bit slow like 2 or 3 times slower than C. (like Java for example). No, really slow, about 10 to 20 times slower than C. Argh… Note it is completely usable for many things.

      +

      Awk

      +

      If you have to “filter” some files and the filter is not too complicated awk is the ideal language to do this. For example, if you want to know which words in a text file are most used. I used it to modify hundreds of XML files in an easier manner than XSLT.

      +

      Perl

      +

      Perl is magic, but the syntax is so hideous nobody can like to work in an environment with many different person in Perl. Or at least, all other collaborators must be excellent programmers. A great feature of perl is its integration with regular expression in its syntax:

      +
      $var =~ s/toto/titi/g
      +

      This program will replace every toto by titi inside the $var variable. The Perl code is often very compact and generally unreadable. But it is a language good to know. It is a kind of awk under steroids.

      +

      Ruby

      +

      Ruby is a very good language. It is often compared (opposed ?) to Python. There are the regular expression operators Perl inside the langage. But the syntax is extremely clear, like in Python. Many feature were inspired by functional programming (as in Python). I used it a lot. It is the worst language I know in term of efficiency. This is the language that lose almost all benchmarks. But it is the perfect tool for prototypes. If you want to make a website prototype, RoR (Ruby on Rails) is certainly one of the best system known to mankind. From idea to realisation, few time will occur. Make this site work for thousands of people, will, on the other hand, certainly require a lot of optimisations.

      +

      One of the greatest Ruby feature is its ability to make the program extremely readable. It is very close to natural language. On the other hand, I found the Object Oriented layer a bit disappointing. The fact there is no real “class variable” but only “tree class variable” for example.

      +

      Considering the community, the ruby one feels closer to the creative than the engineer. I am under the impression designer tends to use Ruby instead of Python.

      +

      Javascript

      +

      It is the good surprise. During years, javascript was considered as an annoying web experience language. In reality, javascript has many really good qualities. Particularly, it is easy to pass a function in parameter and to create anonymous functions (closures). Recently, javascript became far faster than before and many frameworks and libraries appears:

      +
        +
      • Cappuccino, Objective-J (as in objective-C but with javascript)
      • +
      • Sproutcore
      • +
      • Spine.js
      • +
      • Backbone.js
      • +
      • jQuery
      • +
      • prototype.js
      • +
      +

      Particularly with jQuery we can chain functions. It is very nice to use. As I said, this is a good surprise. Javascript was chosen by chance as the script inside your navigator. Instead of the java inspired syntax, everything else is very good. In order to compensate the syntax, you can use CoffeeScript.

      +

      Functional Languages

      +

      CamL

      +

      I learned CamL during the college. It was really interesting. Functional programming is very different to imperative programming (most of popular languages). I had good mathematic intuitions to use this language. But I must confess I never used it for something serious.

      +

      Haskell

      +

      I believe I will still learning this language in many years. I must say it is a pleasure. Generally it takes me no more than some hours to some days to learn a new programming language. Concerning Haskell, this is very different. To master Haskell you need to understand very abstract concepts. Monads and Arrows are some of them. I didn’t understood them before I read some scientific paper. Many week will be necessary to master it perfectly (if someone does). Also the community is very friendly and nice. There is no “LOL! URAN00B! RTFM!” And no concession has been made to make this language more popular (I’m looking at you C++, Java and Javascript). This langage remain pure (I know there are two meaning).

      +

      Concerning making real product with Haskell. In fact, Haskell is very efficient concerning code change. Refactoring code is incredibly easy with Haskell. And in the same time, the Haskell type system helps to make your product bug free.

      +

      Technically this language is close to perfection. But it has some major problems:

      +
        +
      • not so popular
      • +
      • hard to learn
      • +
      • I also believe there is not actually enough success stories with Haskell
      • +
      +

      On the other hand, knowing Haskell will help you learn a lot of thing about programming in general. You should at least take a look. I made an Haskell introduction if you are curious.

      +

      Unpopular Languages

      +

      Metapost

      +

      Metapost is a language to program drawings. What make metapost very good? It contains a linear solver. This is really useful to draw things. For example if you write:

      +
      AA=1/3[A,B]
      +

      It will place the point AA between the point A and B. More precisely at the barycenter (2xA + B)/3.

      +
      X=whatever[A,B]
      +X=whatever[C,D]
      +

      This second example, will place the point X at the intersection of the two segments AB and CD.

      +

      This feature is very helpful, and not only to draw things. Most programming language should think about adding it.

      +

      zsh

      +

      Yes, zsh is a shell. But it is also a script language very well suited to file management. For now, it is the best shell I used. I prefer zsh to bash.

      +

      Prolog

      +

      I never made something serious with Prolog, but I really loved to use and learn it. I had the chance to learn Prolog with Alain Colmerauer himself. This language try to resolve constraints as much as it can. It has a magic feeling when you use it. We only write constraints, we never put order. A bit like functional programming but far more powerful.

      +

      Languages to discover

      +

      Many languages and framework remains to be learnt and tried. Actually I believe I will stay a while with Haskell. Maybe tomorrow I will look at LISP, Scala or Erlang. I also certainly look at clojure to make web application.

      +

      Tell me if you have any other experience with these programming languages. Of course, my feelings are highly subjectives. But I used all of these languages.

      +

      [STL]: Standard Tempate Library[GUI]: Graphic User Interface

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2011-09-28 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/en/latest/index.html b/Scratch/en/latest/index.html new file mode 100644 index 0000000..934d1b7 --- /dev/null +++ b/Scratch/en/latest/index.html @@ -0,0 +1,83 @@ + + + + + + YBlog - latest + + + + + + + + + + + + +
      + + +
      +

      latest

      +
      +
      +
      +
      + +
      + +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/en/rss/index.html b/Scratch/en/rss/index.html new file mode 100644 index 0000000..4df519c --- /dev/null +++ b/Scratch/en/rss/index.html @@ -0,0 +1,97 @@ + + + + + + YBlog - What is this RSS thing? + + + + + + + + + + + + +
      + + +
      +

      What is this RSS thing?

      +
      +
      +
      +
      +

      When you click on this logo:

      +

      rss

      +

      You have the capability to subscribe to my RSS flux. But what it is all about?

      +

      You can visit what is rss or even better look this cool video: RSS explained.

      +
      +

      My explanation

      +

      It is an easy way to aggregate all the update made on websites you like in a single place. You’ll never have to surf many website to see if there is news on a website.

      +

      choose an aggregator

      +

      To begin, you must choose an RSS client called aggregator. There is many online clients: website which will present the news in a fancy way.

      +

      Personally I use Netvibes. I tried a bunch, and it is by far the best - In My Humble Opinion.

      +

      Of course Google has a client named Google Reader. It is great for content you never want to forgot some article. It is not really useful when you have to handle flux generating many news per day.

      +

      Subscribe to some website news

      +

      Once you have chosen your aggregator, you only have to subscribe to websites you like. Do do this, you only have to click on the RSS icon in the top bar of your navigator. Or generally a nice icon is shown into the page you’re reading.

      +

      Get the news

      +

      Now, you’ll only have to use your RSS client. And you’ll see all news on all subscribed websites. There is no more need to surf many websites. All news which interest you are in the same place.

      +
      + +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/en/softwares/index.html b/Scratch/en/softwares/index.html new file mode 100644 index 0000000..9ade5de --- /dev/null +++ b/Scratch/en/softwares/index.html @@ -0,0 +1,87 @@ + + + + + + YBlog - Softwares + + + + + + + + + + + + +
      + + +
      +

      Softwares

      +
      +
      + + +
      + + + + + + + + diff --git a/Scratch/en/softwares/yaquabubbles/index.html b/Scratch/en/softwares/yaquabubbles/index.html new file mode 100644 index 0000000..580d8b6 --- /dev/null +++ b/Scratch/en/softwares/yaquabubbles/index.html @@ -0,0 +1,85 @@ + + + + + + YBlog - YAquaBubbles + + + + + + + + + + + + +
      + + +
      +

      YAquaBubbles

      +
      +
      +
      +
      +

      Screenshot

      +

      YAquaBubbles is a QuartzComposer Screensaver. It was one of my first try but the result was nice.

      +

      YAquaBubbles.dmg

      +
      + +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/en/softwares/yclock/index.html b/Scratch/en/softwares/yclock/index.html new file mode 100644 index 0000000..3f0095a --- /dev/null +++ b/Scratch/en/softwares/yclock/index.html @@ -0,0 +1,85 @@ + + + + + + YBlog - YClock + + + + + + + + + + + + +
      + + +
      +

      YClock

      +
      +
      +
      +
      +

      Screenshot

      +

      YClock is a nice clock screensaver. It has three themes: white, black and red. It is based on a QuartzComposition and with some little Objective-C code to handle gently the frame per second.

      +

      YClock.dmg

      +
      + +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/en/softwares/ypassword/index.html b/Scratch/en/softwares/ypassword/index.html new file mode 100644 index 0000000..9258898 --- /dev/null +++ b/Scratch/en/softwares/ypassword/index.html @@ -0,0 +1,93 @@ + + + + + + YBlog - YPassword + + + + + + + + + + + + +
      + + +
      +

      YPassword

      +
      +
      +
      +
      +

      Easy, Secure and Portable way to manage your web passwords.

      +

      Remember only one strong password. And the rest follow.

      +

      Here you can find:

      + +

      I’ll soon release an iPhone application.

      +
      + +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/en/softwares/ypassword/iphoneweb/index.html b/Scratch/en/softwares/ypassword/iphoneweb/index.html new file mode 100644 index 0000000..7715e10 --- /dev/null +++ b/Scratch/en/softwares/ypassword/iphoneweb/index.html @@ -0,0 +1,91 @@ + + + + + + YBlog - YPassword + + + + + + + + + + + + +
      + + +
      +

      YPassword

      +
      +
      +
      +
      +
      + +
      + + +
      + +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/en/softwares/ypassword/web/index.html b/Scratch/en/softwares/ypassword/web/index.html new file mode 100644 index 0000000..209045c --- /dev/null +++ b/Scratch/en/softwares/ypassword/web/index.html @@ -0,0 +1,91 @@ + + + + + + YBlog - YPassword + + + + + + + + + + + + +
      + + +
      +

      YPassword

      +
      +
      +
      +
      +
      + +
      + + +
      + +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/en/validation/index.html b/Scratch/en/validation/index.html new file mode 100644 index 0000000..bce9d42 --- /dev/null +++ b/Scratch/en/validation/index.html @@ -0,0 +1,87 @@ + + + + + + YBlog - Validation + + + + + + + + + + + + +
      + + +
      +

      Validation

      +
      +
      +
      +
      +

      One word to explain why there is some validation errors.

      +

      I wanted to use box-shadows and border-radius.

      +

      Then here I prefered pragmatism against dogmatism.

      +

      Using these properties broke my validation but work really well in the two most recent and main browsers (Safari 4 and Firefox 3.5 at the time I’m writing these lines). If you don’t use these browser the page is correctly displayed but not with all the ‘eyecandy’ effects.

      +

      I believed in the benefits of CSS validation, this is why there is alway the CSS validation link on my pages. And all CSS validate except for properities beginning by -moz and -webkit.

      +
      + +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/files/YAquaBubbles.dmg b/Scratch/files/YAquaBubbles.dmg new file mode 100644 index 0000000..67ba05b Binary files /dev/null and b/Scratch/files/YAquaBubbles.dmg differ diff --git a/Scratch/files/YClock.dmg b/Scratch/files/YClock.dmg new file mode 100644 index 0000000..3ca0ac4 Binary files /dev/null and b/Scratch/files/YClock.dmg differ diff --git a/Scratch/files/YPassword-1.6.zip b/Scratch/files/YPassword-1.6.zip new file mode 100644 index 0000000..769002d Binary files /dev/null and b/Scratch/files/YPassword-1.6.zip differ diff --git a/Scratch/files/YPassword-1.7.zip b/Scratch/files/YPassword-1.7.zip new file mode 100644 index 0000000..ad3039e Binary files /dev/null and b/Scratch/files/YPassword-1.7.zip differ diff --git a/Scratch/files/YPassword-1.8.zip b/Scratch/files/YPassword-1.8.zip new file mode 100644 index 0000000..097e8dd Binary files /dev/null and b/Scratch/files/YPassword-1.8.zip differ diff --git a/Scratch/files/cv.pdf b/Scratch/files/cv.pdf new file mode 100644 index 0000000..1a3bdeb Binary files /dev/null and b/Scratch/files/cv.pdf differ diff --git a/Scratch/files/cv_en.pdf b/Scratch/files/cv_en.pdf new file mode 100644 index 0000000..0ad00a4 Binary files /dev/null and b/Scratch/files/cv_en.pdf differ diff --git a/Scratch/files/forcePaste.app.zip b/Scratch/files/forcePaste.app.zip new file mode 100644 index 0000000..8cd3cc3 Binary files /dev/null and b/Scratch/files/forcePaste.app.zip differ diff --git a/Scratch/fr/about/contact/index.html b/Scratch/fr/about/contact/index.html new file mode 100644 index 0000000..6a8991c --- /dev/null +++ b/Scratch/fr/about/contact/index.html @@ -0,0 +1,92 @@ + + + + + + YBlog - Contact + + + + + + + + + + + + +
      + + +
      +

      Contact

      +
      +
      +
      +
      +
      + +
      +

      Comment me contacter

      +

      Avatar

      +

      yann.esposito@gmail.com
      Suivez moi sur twitter
      Mes “bookmarks” pinboard
      Open Source github
      stackoverflow

      +
      + +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/about/index.html b/Scratch/fr/about/index.html new file mode 100644 index 0000000..9b9fcb1 --- /dev/null +++ b/Scratch/fr/about/index.html @@ -0,0 +1,136 @@ + + + + + + YBlog - Qui est derrière ce site? + + + + + + + + + + + + +
      + + +
      +

      Qui est derrière ce site?

      +
      +
      +
      +
      +
      + +
      +

      Pour mieux me connaître

      +
      +Une photo de moi

      Une photo de moi

      +
      + + + + + + + + + + + + + + + + + + + +
      NomYann Esposito
      DiplômeDoctorat d’Informatique
      ÉcoleUniversité de Provence
      EmploisInformaticien à Sophia Antipolis
      +

      Livres que j’aime :

      +
        +
      • Goëdel, Escher & Bach [Hofstadter]
      • +
      • On Numbers And Games [Conway]
      • +
      • Baudolino [Eco]
      • +
      +

      Films que j’aime :

      +
        +
      • Eraserhead [Lynch]
      • +
      • Mullholland Drive [Lynch]
      • +
      • Naked Lunch [Cronenberg]
      • +
      • eXistenZ [Cronenberg]
      • +
      +

      Mes bookmarks sur pinboard

      +

      Mon cv (en anglais)

      +

      En deux mots

      +

      Je suis un passioné. Passioné par :

      + +

      Mais avant tout j’adore apprendre. Par exemple, j’ai appris de nombreux langages de programmation: Haskell, Python, Ruby, C, C++, Objective-C, Java, Perl, awk, bash, zsh, LaTeX, Metapost, CamL

      +
      + +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/about/old/index.html b/Scratch/fr/about/old/index.html new file mode 100644 index 0000000..3d165cf --- /dev/null +++ b/Scratch/fr/about/old/index.html @@ -0,0 +1,94 @@ + + + + + + YBlog - Autres sites + + + + + + + + + + + + +
      + + +
      +

      Autres sites

      +
      +
      + + +
      + + + + + + + + diff --git a/Scratch/fr/about/technical_details/index.html b/Scratch/fr/about/technical_details/index.html new file mode 100644 index 0000000..3a5ae61 --- /dev/null +++ b/Scratch/fr/about/technical_details/index.html @@ -0,0 +1,93 @@ + + + + + + YBlog - Détails techniques + + + + + + + + + + + + +
      + + +
      +

      Détails techniques

      +
      +
      +
      +
      +
      + +
      +

      Ce site a été réalisé à partir de rien ou presque. Quasiment toutes les pages ont été codées avec Vim et engendrées avec nanoc.

      +

      Les images ont été faites avec Inkscape et Gimp. J’utilise Git comme DCVS.

      +

      Les commentaires de blog sont gérés par disqus intense debate. Ainsi je n’ai besoin pour héberger mons site que d’un serveur de pages statiques. Ce qui a de nombreux avantages. Principalement concernant la charge et la sécurité du serveur.

      +

      Si vous n’avez pas tout compris, rappelez-vous simplement que je n’utilise que des outils Opensource et que j’ai conçu ce site quasiment ex nihilo.

      +
      + +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/01_nanoc/index.html b/Scratch/fr/blog/01_nanoc/index.html new file mode 100644 index 0000000..c889bb7 --- /dev/null +++ b/Scratch/fr/blog/01_nanoc/index.html @@ -0,0 +1,127 @@ + + + + + + YBlog - nanoc + + + + + + + + + + + + +
      + + +
      +

      nanoc

      +
      +
      +
      +
      +

      Qu’est-ce que nanoc ?

      +

      Il ne s’agit pas exactement d’un CMS, mais plutôt d’un système de gestion de pages statiques.

      +

      Il faut programmer sois-même les pages web, le code pour engendrer les menus…

      +

      J’ai programmé des filtres pour rendre ce site multilangue par exemple

      +

      Vous pourrez trouver beaucoup d’informations sur le site officiel de nanoc.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2008-10-10 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/02_ackgrep/index.html b/Scratch/fr/blog/02_ackgrep/index.html new file mode 100644 index 0000000..c9558c7 --- /dev/null +++ b/Scratch/fr/blog/02_ackgrep/index.html @@ -0,0 +1,142 @@ + + + + + + YBlog - Mieux que grep + + + + + + + + + + + + +
      + + +
      +

      Mieux que grep

      +
      +
      +
      +
      +

      Mise à jour

      +

      Comme Andy Lester me l’a fait remarqué. ack est un simple fichier perl qu’il suffit de copier dans son répertoire personnel ~/bin. Maintenant j’ai ack sur mon serveur professionnel.

      +

      Il suffit d’aller sur http://betterthangrep.com pour le télécharger.

      +

      Sincèrement, je ne comprend pas qu’ack ne soit pas une commande implémentée par défaut sur les systèmes UNIX. Je ne peux vraiment plus m’en passer, il m’est devenu aussi essentiel qu’un which ou un find.

      +
      +

      Mieux que grep

      +

      Un des mes usages principaux de grep est

      +
      + + grep ‘pattern’ */(.) +
      + +

      La plupart du temps c’est suffisant, mais ajouter de la coloration améliore beaucoup l’utilité de cette commande. Il existe déjà un outil pour ça : il s’appelle ack-grep sous Ubuntu. Comme je ne peux pas l’installer sur le serveur de mon entreprise, j’en ai créé un moi-même en quelques lignes :

      +
      + +

      #!/usr/bin/env zsh (($#<1)) && { print ‘usage: ack “regexp”’ >&2; exit 1 }

      +listeFic=( /(.) ) autoload zargs zargs – $listeFic – grep $1 | perl -ne ‘use Term::ANSIColor; if (m/([^:])(:.)(’$1’)(.)/) { print color(“green”).$1; print color(“reset”).$2; print color(“black”,“on_yellow”).$3; print color(“reset”).$4.“”; } ’ +
      + +

      Pour mon utilisation personnelle et celle de mon équipe c’est suffisant. J’espère que ça pourra vous aider.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-07-22 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/03_losthighway/index.html b/Scratch/fr/blog/03_losthighway/index.html new file mode 100644 index 0000000..167e308 --- /dev/null +++ b/Scratch/fr/blog/03_losthighway/index.html @@ -0,0 +1,201 @@ + + + + + + YBlog - Lost Highway démystifié (un peu) + + + + + + + + + + + + + +
      + + +
      +

      Lost Highway démystifié (un peu)

      +
      +
      +
      +
      +

      Lost Highway

      +
      +Lost Highway ne laisse pas indiférent. Le revoir ne lasse pas même s’il parrait complètement obscur. C’est une des forces de David Lynch. Il faut garder à l’esprit qu’il n’existe pas une seule interprétation possible et cohérente du film. Seul David Lynch pourrait donner une explication complète du film. Je donne cependant quelques clés que j’ai découverte qui aident à suivre le film sans être complètement perdu. Ces clés devraient vous aider à vous faire votre propre idée du film… +
      + +

      La première fois que j’ai vu Lost Highway je me suis senti un peu perdu. J’en ai alors cherché le sens. Voilà ce que j’ai pu trouver sur Internet :

      +
        +
      • Fred passe un pacte avec le diable incarné par l’homme en noir ;
      • +
      • l’homme mystérieux est une (la) caméra ;
      • +
      • seule la première histoire est vrai, la suite étant l’imagination de Fred ;
      • +
      +

      sans compter les multiples avis trouvés sur les forums. Tout cela ne me paraissait pas convaincant. J’ai alors réussi à trouver deux articles (en anglais) qui proposent de bien meilleures interprétations. Mais aucun des deux ne m’a complètement convaincu :

      +
        +
      • le permier est sur mediacircus,
      • +
      • le second qui développe presque la même interprétation que la première est vraiment très détaillé sur jasonweb.
      • +
      +

      Il faut garder à l’esprit qu’il n’existe pas une seule interprétation possible et cohérente du film. Seul David Lynch pourrait donner l’explication complète du film.

      +

      Je donne quelques clés aidant à suivre le film sans être complètement perdu. Ces clés devraient vous aider à vous faire votre propre idée du film.

      +

      Le test de Rorschach

      +

      test de Rorschach À l’instar du protagoniste chacun voit dans ce film ce qu’il a envie d’y voir. Nous pouvons nous y perdre simplement parce que nous pouvons nous perdre dans notre propre esprit. C’est une invitation à la réflexion. Regarder ce film c’est un peu comme passer un test de Rorschach. Qu’y voit-on ? Chacun y met un peu de sa propre personnalité dans l’explication du film.

      +
        +
      • Si vous êtes un mystique, vous verrez dans l’homme mystérieux un démon
      • +
      • si vous êtes plus psychanalytique vous y verrez une partie inconsciente du protagoniste…
      • +
      +

      En général en essayant d’expliquer ce film, on se perd un peu dans notre pensée. Et souvent on échoue à tout expliquer. Il y a toujours un point qui rend la construction incohérente avec le film. C’est pourquoi rechercher une explication unique est un entreprise vaine.

      +

      Interprétation ≠ Explication

      +

      Je donne une interprétation et non pas une explication. Ma vision des choses, me semble cohérente. Cependant il est très probable que mon adhésion au film soit très différente de la votre. Il y a certainement beaucoup d’autres explications qui restent cohérentes.

      +

      J’écris cet article, parce que j’ai l’impression d’en avoir trouver une qui marche pour plus de 97% du film (peut-être 100%, mais j’en doute, il faudrait que je le revois encore une fois).

      +

      Les clefs du films

      +
      + + Tout se passe dans la mémoire de Fred +
      + +

      Tout d’abord, il est clair que comprendre le film comme simplement un film fantastique ne fonctionne pas. En suivant ce point d’entrée on en fini pas de se heurter à des détails incompréhensibles.

      +

      Mon hypothèse de départ c’est que le film dépeint la représentation de la réalité que s’en fait Fred. Chaque fois qu’il essaye d’échapper à la réalité, celle-ci finira par le rattraper.

      +

      Fred a commis un acte horrible, un meurtre, et essaye de réparer sa mémoire pour accepter son acte. Il va alors s’inventer des réalitées alternatives.

      +
        +
      • Dans un premier temps il tue sa femme (Renée) parce qu’elle le trompe.
      • +
      • Dans la deuxième partie, il est plus faible. La version blonde de Renée va le manipuler pour tuer Dick Laurent.
      • +
      • Dans la troisième partie il tue Dick Laurent
      • +
      +

      Quelle est la validité de ce choix ?

      +

      Cette interprétation me semble valide à cause du dialogue au début du film avec les policiers qui demandent au protagoniste s’il a une caméra :

      +
      +

      “Do you own a video camera?”
      “No, Fred hates them.”
      “I like to remember things my own way.”
      “What do you mean by that?”
      “How I remember them, not necessarily the way they happened.”

      +
      +

      Ce que l’on peut traduire approximativement par :

      +
      +

      – Avez-vous une caméra ?
      – Non, Fred les détestes.
      – J’aime me rappeler les choses à ma façon.
      – Qu’entendez-vous par là ?
      – Je me rappelle des choses pas nécessairement comme elles se sont passées.

      +
      +

      Ainsi, ce que l’on voit n’est pas la réalité, mais la réalité telle que le conçoit Fred. Il est donc le Dieu de cette réalité. Ainsi les interprétations mystiques faisant intervenir le Diable ont une certaine validité.

      +

      Qui est l’homme mystérieux ?

      +

      +

      Qui est donc ce personnage étrange et inquiétant ? Un être capable d’ubiquité qui dit être invité par Fred dans sa maison ? Sans sourcils, le visage blême, les yeux écarquillés fixant sans relâche les faits et gestes de Fred.

      +

      C’est certainement une des clés du film. À mon avis, il représente la partie mauvaise de Fred. Certainement la jalousie. Si j’étais Catholique je dirai Satan, le tentateur. Il n’agit jamais, mais ne fait qu’observer et filmer. Par contre c’est lui qui donne les armes à Fred pour tuer Dick Laurent. Fred l’a laissé entrer chez lui et il ne peut plus s’en débarrasser. Un peu comme le Iago de Shakespeare est enfermé dans sa jalousie. Le personnage mystérieux prend toute l’importance, il le ronge de l’intérieur. Il aide Fred à accomplir les actes de violences et aussi l’oblige à se souvenir de la réalité.

      +

      Quand il fait l’amour à Renée il voit le visage de l’homme mystérieux à la place du visage de sa femme. En réalité, il s’agit de la même personne d’après Fred. Ce serait donc elle qui est la source de son mal intérieur.

      +

      Qui filme et dépose les cassettes ?

      +

      C’est certainement l’homme mystérieux (ou Fred lui-même) qui est à l’origine de ces cassettes. Le rôle des cassettes est double :

      +
        +
      • Rappeler à Fred la réalité. D’après Fred les cassettes video correspondent à la réalité. Il a beau essayer de se cacher la réalité, les cassettes finissent par aller jusqu’au bout et il se voit en train de tuer Renée.
      • +
      • La cassette peut aussi faire référence aux cassettes de films pornographique dans laquelle Renée a peut-être tournée dans la réalité ?
      • +
      +

      Que s’est-il vraiment passé ?

      +

      Ici, tout n’est pas donné, on garde une assez grande liberté. Mais on a des indices.

      +

      Hypothese n°1

      +

      Je dirais que le protagoniste est un garagiste qui est tombé amoureux d’une actrice porno. Il l’a certainement vu la première fois accompagnant le fameux Dick Laurent. Voyant qu’il ne peut pas l’avoir pour lui, fou de jalousie il tue Dick Laurent dans un motel où celui-ci à couché avec Renée.

      +

      On a la liberté de décider s’il a vraiment tué la femme ou pas. Dans ma première vision du film, j’avais envie de dire qu’il ne la tue pas. Mais qu’une fois le meurtre commis, il va chez elle, sonne pour lui annoncer la mort de Dick Laurent. Il a alors juste le temps de s’enfuir, la police à ses trousses.

      +

      Hypothese n°2

      +

      La première partie resemble à la réalité. Il a vraiment tué sa femme. Il se fait arrété et condamné (certainement à mort). Par contre on ne sait pas s’il est aussi allé tuer Andy.

      +

      alors c’est laquelle ?

      +

      La seconde hypothèse me semble plus vraisemblable, car il y a plus de recoupements possibles. La première me semble aussi cohérente. C’est cette première hypothèse que j’avais émise lors de mon premier visionnage du film.

      +

      Ce qui montre la force de ce film c’est de se dire qu’il y a de nombreuses autres hypothèses qui pourraient aussi bien fonctionner. C’est le fameux effet Rashomon. Plusieurs personnes peuvent décrire de façon cohérentes ce qu’elles ont vu, mais toutes les descriptions sont incohérentes entres-elles.

      +
      +

      Conclusion

      +

      Il y aurait encore beaucoup à dire sur l’analyse de ce film. Mais il me semble que j’ai rassemblé l’essentiel des clés pour sa compréhension.

      +

      Il me semble qu’avoir à l’esprit l’effet “test de Rorschach” est essentiel lors de la visualisation de ce film.

      +

      J’aimerai avoir votre opinion ; mon interprétation tient-elle la route ?

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-08-04 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/04_drm/index.html b/Scratch/fr/blog/04_drm/index.html new file mode 100644 index 0000000..cb5aa04 --- /dev/null +++ b/Scratch/fr/blog/04_drm/index.html @@ -0,0 +1,138 @@ + + + + + + YBlog - Les protections anti-copies sont LE MAL + + + + + + + + + + + + + +
      + + +
      +

      Les protections anti-copies sont LE MAL

      +
      +
      +
      +
      +

      Protections anti-copie = Belle connerie (+1)!

      +

      Ma femme a acheté pour environ 500€ (au moins) de séries télé sur iTunes. Mais elles s’est trompé pour la première saison de Battlestar Galactica. Qu’elle a téléchargé en anglais. Hors comme les séries sont protégées, on ne peut simplement pas voir la série avec des sous-titres !

      +
      +

      +WTF? +

      +
      + +

      Résultat des courses, ma femme n’achetera plus de séries sur iTunes tant qu’elles resteront protégées par DRM, qu’elle ne pourront pas être gravées sur DVD (pour être regardées sur la télé). Et comme elle est bien moins prompte à acheter des DVD que simplement cliquer sur un bouton.

      +
      + +

      Ca fera nettement moins d’argent pour vous les ayant-droits !!!

      +
      + +

      Et ma femme ne pourra pas voir ces épisodes.
      C’est ce qu’on appelle une cooperation ‘LOSE-LOSE’.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-08-15 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/05_git_create_remote_branch/index.html b/Scratch/fr/blog/05_git_create_remote_branch/index.html new file mode 100644 index 0000000..a4b2e03 --- /dev/null +++ b/Scratch/fr/blog/05_git_create_remote_branch/index.html @@ -0,0 +1,134 @@ + + + + + + YBlog - Création de branches externe avec Git + + + + + + + + + + + + + +
      + + +
      +

      Création de branches externe avec Git

      +
      +
      +
      +
      +

      Créer une branche Git externe facilement

      +

      J’utilise Git pour synchroniser des projets personnels. C’est pourquoi quand je crée une branche locale je souhaite quasiment toujours qu’elle soit aussi créée en externe (remote).

      +

      Voici le script que j’utilise pour accomplir cette tâche :

      +
      + +

      #!/usr/bin/env zsh

      +

      if (($#<1)); then print – “usage: $0:t branch_name” >&2 exit 1 fi

      +branch=1gitbr{branch} git co branchgitconfigbranch. {branch}.remote origin git config branch.branch. mergerefs / heads / {branch}
      +
      + +

      Bien sûr, je suppose qu’origin est déjà configurée.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-08-17 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/06_How_I_use_git/index.html b/Scratch/fr/blog/06_How_I_use_git/index.html new file mode 100644 index 0000000..d900a2a --- /dev/null +++ b/Scratch/fr/blog/06_How_I_use_git/index.html @@ -0,0 +1,252 @@ + + + + + + YBlog - Git en solo + + + + + + + + + + + + + +
      + + +
      +

      Git en solo

      +
      +
      +
      +
      +

      central architecture

      +
      + +

      Màj : Actuellement j’utilise github avec des repository privés. Je paye une somme très raisonnable pour ce service. Si vous voulez être complètement autonome, je vous conseille d’utiliser gitolite sur votre propre serveur accessible sur le web.

      +
      + +

      J’utilise Git pour gérer mes projets personnels. J’ai un repository centralisé et tous mes ordinateurs se synchronisent avec lui. Cependant, dans la documentation officielle, je n’ai pas trouvé clairement ce que je souhaitais.

      +

      En d’autres termes, si vous souhaitez utiliser le type de workflow que SVN proposait avec Git (et ses avantages), voici comment procéder.

      +
      +

      Initialisation

      +

      Disons que j’ai déjà un projet et que je veuille en créer un nouveau.

      +
      +
      cd to/project/directory/
      +git init
      +git add
      +git commit
      +
      + +

      Maintenant tous les fichiers du répertoire to/project/directory/ sont versionnés. Si vous voulez ignorer certains fichiers il suffit de modifier le fichier .gitignore.

      +Par exemple voici le mien : +
      +
      *.swp
      +.DS_Store
      +ikog.py.bak
      +output/Scratch/assets
      +output/Scratch/en
      +output/Scratch/fr
      +output/Scratch/multi
      +
      + +

      Ensuite, il faut placer ce projet dans un répertoire accessible via Internet.

      +
      +
      git clone --bare . /path/to/repository
      +
      + +
      + Màj: La meilleure solution est d’installer gitolite pour installer un serveur git sur sa machine. Gitolite permet de gérer la gestion des droits d’utilisateurs, ceux-ci n’ayant pas accès à un shell sur la machine. +
      + +

      Maintenant à partir de n’importe quel ordinateur, voici ce que vous pouvez faire :

      +
      +
      git clone protocol://path/to/repository local_directory
      +
      + +

      et local_directory contiendra un projet à jour.

      +
      +

      +Je vous conseille de faire la même opération sur l’ordinateur qui à servi à créer le projet de façon à vérifier que tout fonctionne correctement. +
      + +
      +

      L’utilisation courante

      +

      Pour résumer vous avez maintenant un repository sur Internet et un ou plusieurs ordinateurs lui sont associés. Maintenant il faut que tout soit toujours synchronisé.

      +

      Avant de commencer à travailler, la première chose à faire est de récupérer les modification à partir d’Internet vers votre poste local :

      +
      +
      git pull
      +
      + +

      Ensuit vous pouvez travailler en faisant (plusieurs fois) :

      +
      +
      hack, hack, hack...
      +git add some files
      +git commit
      +
      + +

      Quang vous voulez envoyez les modifications locales sur Internet, il suffit de faire :

      +
      +
      git push
      +
      + +

      Tout devrait être bon.

      +

      Si vous avez des problèmes avec le push et le pull ; vérifiez votre fichier .git/config. Il devrait contenir les lignes suivantes :

      +
      +
      ...
      +[remote "origin"]
      +	url = protocol://url/of/the/repository
      +	fetch = +refs/heads/*:refs/remotes/origin/*
      +[branch "master"]
      +	remote = origin
      +	merge = refs/heads/master
      +...
      +
      + +

      Synchronisation des branches

      +

      Bien, maintenant que tout semble bon, il faut encore s’occuper de quelques petites choses (sinon, SVN suffirait). Git est complètement orienté sur la décentralisation et la création de nouvelles branches sur le même poste. Synchroniser des branches sur plusieurs serveurs différent n’est pas une opération naturelle.

      +

      C’est pourquoi j’ai créé deux simples scripts pour automatiser cette opération. Un script pour créer un branche localement et en ligne. Un autre script pour récupérer les branches en lignes qui ne sont pas présente localement.

      +

      Ainsi, lorsque je veux créer une nouvelle branche (localement et ligne) ; je lance le script :

      +
      +git-create-new-branch branch_name +
      + +

      et quand je suis sur un autre ordinateur et que je veux récupérer les branches crées sur un autre poste, j’exécute :

      +
      +git-get-remote-branches +
      + +

      Voici le code des deux script (en zsh) :

      +
      +
      #!/usr/bin/env zsh
      +
      +if (($#<1)); then
      +    print -- "usage: $0:t branch_name" >&2
      +    exit 1
      +fi
      +
      +branch=$1
      +git br ${branch}
      +git co ${branch}
      +git config branch.${branch}.remote origin
      +git config branch.${branch}.merge refs/heads/${branch}
      +
      + +
      +
      #!/usr/bin/env zsh
      +
      +# recup branches not on local
      +localbranches=( $(git br | sed 's/\*/ /') )
      +remoteMissingBranches=( $(git br -r | \
      +    egrep -v "origin/HEAD|(${(j:|:)localbranches})" ) )
      +for br in $remoteMissingBranches; do
      +  branch=${br#origin/}
      +  print "get remote branch $branch"
      +  git br ${branch}
      +  git config branch.${branch}.remote origin
      +  git config branch.${branch}.merge refs/heads/${branch}
      +done
      +
      + + +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-08-18 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/07_Screensaver_compilation_option_for_Snow_Leopard/index.html b/Scratch/fr/blog/07_Screensaver_compilation_option_for_Snow_Leopard/index.html new file mode 100644 index 0000000..f8ea620 --- /dev/null +++ b/Scratch/fr/blog/07_Screensaver_compilation_option_for_Snow_Leopard/index.html @@ -0,0 +1,128 @@ + + + + + + YBlog - Compilation d'économiseur d'écran sous Snow Leopard<small>©</small> + + + + + + + + + + + + + +
      + + +
      +

      Compilation d'économiseur d'écran sous Snow Leopard©

      +
      +
      +
      +
      +

      Comment recompiler un économiseur d’écran sous Snow Leopard(c)

      +

      Mon économiseur d’écran ne fonctionnait plus sous Mac OS X 10.6 Snow Leopard(c). Après un peu de recherche sous google, le problème semblait pouvoir être réglé avec une recompilation. Cependant, même en recomilant en 64 bits ça ne fonctionnait toujours pas. Après un peu plus de recherches (merci à ElectricSheep ), j’ai découvert les bons paramètres.

      +

      XCode configuration

      +

      Pour l’instant je ne l’ai pas compilé pour être compatible Tiger et Leopard. Je ne connais pas assez bien XCode pour savoir comment désactiver le garbage collector sur la version 32 bits et l’activer sur la version 64 bits.

      +

      Il a été assez difficile de découvrir toutes ces informations. J’espère que cet article aura pu aider quelqu’un.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-09-06 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/index.html b/Scratch/fr/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/index.html new file mode 100644 index 0000000..75d3468 --- /dev/null +++ b/Scratch/fr/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/index.html @@ -0,0 +1,179 @@ + + + + + + YBlog - ssh sur le port 443 avec Snow Leopard + + + + + + + + + + + + + +
      + + +
      +

      ssh sur le port 443 avec Snow Leopard

      +
      +
      +
      +
      +

      Surfez partout comme si vous étiez chez vous

      +

      Que ce soit pour surfer en toute sécurité depuis un accès wifi non sécurisé ou pour contourner les parefeux diaboliques des entreprises. J’ai configuré un serveur ssh écoutant sur le port 443 chez moi.

      +

      Ensuite de mon portable ou de mon ordinateur local, je dois simplement lancé la merveilleuse commande :

      +
      +
      ssh -p 443 -D 9050 username@host
      +
      + +

      et un proxy socks écoute sur le port 9050. Ce proxy socks transférera toutes les requêtes locales via le tunnel ssh. Ainsi je peux surfer en local comme si je naviguais depuis mon ordinateur à la maison. Je peux écrire mon numéro de carte bleu sans avoir peur que le wifi local soit sniffé. Je dois simplement configurer mon navigateur web pour utiliser le proxy socks sur localhost écoutant le port 9050.

      +

      J’ai eu cette information à partir de cet article.

      +

      Ssh et Snow Leopard(c)

      +

      J’ai un Mac avec Snow Leopard(c) à la maison. Il ne suffit pas de modifier le fichier /etc/sshd.config pour changer le port d’écoute d’sshd. Le système utilise launchd pour lancer les démons.

      +

      J’ai posé cette question sur Apple Discussions dans ce fil de discussion. Merci à tous ceux qui m’ont aidé. Et la solution est :

      +

      Créer un fichier /Library/LaunchDaemons/ssh-443.plist contenant :

      +
      +
      <?xml version="1.0" encoding="UTF-8"?>
      +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
      +<plist version="1.0">
      +<dict>
      +	<key>Disabled</key>
      +	<false/>
      +	<key>Label</key>
      +	<string>local.sshd</string>
      +	<key>Program</key>
      +	<string>/usr/libexec/sshd-keygen-wrapper</string>
      +	<key>ProgramArguments</key>
      +	<array>
      +		<string>/usr/sbin/sshd</string>
      +		<string>-i</string>
      +	</array>
      +	<key>Sockets</key>
      +	<dict>
      +		<key>Listeners</key>
      +		<dict>
      +			<key>SockServiceName</key>
      +			<string>https</string>
      +		</dict>
      +	</dict>
      +	<key>inetdCompatibility</key>
      +	<dict>
      +		<key>Wait</key>
      +		<false/>
      +	</dict>
      +	<key>StandardErrorPath</key>
      +	<string>/dev/null</string>
      +        <key>SHAuthorizationRight</key>
      +        <string>system.preferences</string>
      +</dict>
      +</plist>
      +
      + +

      C’est une copie de /System/Library/LaunchDaemons/ssh.plist avec quelques modifications :

      +
        +
      • le SockServiceName est devenu https au lieu de ssh
      • +
      • le Label est passé de com.openssh.sshd à quelque chose qui n’existait pas comme local.sshd
      • +
      +

      Encore une fois j’espère que ça a pu être utile.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-09-07 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/09_Why_I_didn-t_keep_whosamung-us/index.html b/Scratch/fr/blog/09_Why_I_didn-t_keep_whosamung-us/index.html new file mode 100644 index 0000000..72844c0 --- /dev/null +++ b/Scratch/fr/blog/09_Why_I_didn-t_keep_whosamung-us/index.html @@ -0,0 +1,134 @@ + + + + + + YBlog - Pourquoi je n'ai pas conservé whos.amung.us + + + + + + + + + + + + + +
      + + +
      +

      Pourquoi je n'ai pas conservé whos.amung.us

      +
      +
      +
      +
      +

      J’ai arrété d’utiliser whos.amung.us en faveur de Google Analytics.

      +

      La plupart du temps je préfère ne pas utiliser le même produit que tout le monde. J’aime bien essayer des choses un peu nouvelles. Mais whosamung.us avait trop de publicités. Je devais affichier une de leur image sur mon site qui n’écrivait que le nombre de personne actuellement présentes. Pas les nombres de visites.

      +

      C’est pourquoi j’utilise maintenant google analytics. Le problème reste entier pour les navigateurs sans javascript.

      +

      Donc pour l’instant

      +
      +Théorème :
      +
      +Google Analytics > Who’s Amung Us +
      + + +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-09-11 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/10_Synchronize_Custom_WebSite_with_mobileMe/index.html b/Scratch/fr/blog/10_Synchronize_Custom_WebSite_with_mobileMe/index.html new file mode 100644 index 0000000..2ff5634 --- /dev/null +++ b/Scratch/fr/blog/10_Synchronize_Custom_WebSite_with_mobileMe/index.html @@ -0,0 +1,352 @@ + + + + + + YBlog - Héberger son site personnel sur le site mobileMe + + + + + + + + + + + + + +
      + + +
      +

      Héberger son site personnel sur le site mobileMe

      +
      +
      +
      +
      +

      Mise à jour du (2012/01/11)

      +

      iDisk va bientôt disparaître. Cet article est donc presque complètement obsolète.

      +

      mise à jour du 28/10/2009

      +

      J’ai mis à jour mon script avec une version incrémentale bien plus pratique. En plus depuis l’écriture de cet article Apple(c) semble avoir nettement amélioré la vitesse de ses serveurs en Europe.

      +
      +

      WebDav terror

      +

      En France l’iDisk d’Apple(c) est très lent. La vitesse d’upload me rapelle l’époque des modem 56k, c’est dire. La plupart du temps les opérations telles que lister le contenu d’un répertoire prennent au moins 30 secondes (pour 15 éléments). Renommer un répertoire échoue presque systématiquement.

      +

      Apple(c) utilise des serveurs WebDav pour héberger les fichiers. Le protocole fonctionne sur le port 80 (comme http). Je me suis rendu compte qu’utiliser WebDav via https fontionne bien mieux (2 à 3 fois plus rapide avec moins d’erreurs). Mais, ça reste quand même très lent et insuffisant.

      +

      J’uploade mes fichiers à partir de mon Mac et de temps en temps à partir d’un PC sous Ubuntu (iDisk monté avec webdavfs).

      +

      Synchroniser de façon sûre

      +

      Voici le script que j’utilise pour synchroniser mon site web (non créé avec iWeb(c)) avec le maximum de sécurité. Chaque opération est répétée jusqu’à ce qu’elle fonctionne.

      +

      Les idées sont :

      +
        +
      • Synchroniser vers un répertoire temporaire sur le serveur distant, puis “swapper” les noms des répertoires. Ainsi le site ne reste indisponible que le temps du “swap” du nom des deux répertoires.
      • +
      • Réitérer toutes les opérations jusqu’à ce qu’elle aient réussi (par exemple pour le renommage)
      • +
      +

      Jusqu’ici j’utilise rsync qui n’est en fait pas plus efficace qu’une simple copie cp avec WebDav. Je devrais utiliser une méthode pour mémoriser les changements entre chaque publication.

      +

      En réalité quand je suis sur mon Mac j’utilise Transmit qui est vraiment très bien et surtout beaucoup plus efficace que le finder pour synchroniser des fichiers. Ensuite, je ne fait que le “swap” des répertoires.

      +

      Mon script prend un paramètre -s pour ne faire que le “swap”. Il prend aussi une option -a pour envoyer le fichier index.html qui va rediriger vers ma nouvelle page principale (iWeb(c) à la fâcheuse habitude de le remplacer).

      +

      Pour utiliser le script vous devriez remplacer la valeur de la variable mobileMeUser par votre nom d’utilisateur mobileMe(c).

      +
      +
      #!/usr/bin/env zsh
      +
      +# Script synchronisant le site sur me.com
      +# normalement, le site est indisponible le moins de temps possible
      +# le temps de deux renommages de répertoire
      +
      +mobileMeUser="yann.esposito"
      +siteName="siteName"
      +
      +# Depending of my hostname the 
      +if [[ $(hostname) == 'ubuntu' ]]; then
      +    iDisk='/mnt/iDisk'
      +else
      +    iDisk="/Volumes/$mobileMeUser"
      +fi
      +
      +root=$HOME/Sites/$siteName
      +destRep=$iDisk/Web/Sites/$siteName
      +
      +[[ ! -d $root ]] && { 
      +    print -- "$root n'existe pas ; vérifiez la conf" >&2; 
      +    exit 1 
      +}
      +
      +[[ ! -d $destRep ]] && { 
      +    print -- "$destRep n'existe pas, veuillez remonter le FS" >&2; 
      +    exit 1 
      +}
      +
      +if [[ $1 == '-h' ]]; then
      +    print -- "usage: $0:h [-h|-a|-s]"
      +    print -- "  -a sychronise aussi l'index"
      +    print -- "  -h affiche l'aide"
      +    print -- "  -s swappe simplement les répertoires"
      +fi
      +
      +if [[ $1 == '-a' ]]; then
      +    print -- "Synchronisation de l'index (${destRep:h})"
      +    rsync -av $root/index.html ${destRep:h}/index.html
      +fi
      +
      +print -- "Root = $root"
      +print -- "Dest = $destRep"
      +
      +if [[ ! $1 = '-s' ]]; then
      +    [[ ! -d $destRep.tmp ]] && mkdir $destRep.tmp
      +    print -P -- "%B[Sync => tmp]%b"
      +    result=1
      +    essai=1
      +    while (( $result > 0 )); do
      +        rsync -arv $root/Scratch/ $destRep.tmp
      +        result=$?
      +        if (( $result > 0 )); then
      +            print -P -- "%BEchec du rsync%b (essai n°$essai)" >&2
      +        fi
      +        ((essai++))
      +    done
      +fi
      +
      +# SWAP
      +print -P -- "%B[Swap des Répertoires (tmp <=> target)]%b"
      +essai=1
      +while [[ -e $destRep.old ]]; do
      +    print -n -- "suppression de $destRep.old"
      +    if ((essai>1)); then 
      +        print " (essai n°$essai)"
      +    else
      +        print
      +    fi
      +    ((essai++))
      +    \rm -rf $destRep.old
      +done
      +
      +print -- "  renommage du repertoire sandard vers le .old"
      +essai=1
      +while [[ -e $destRep ]]; do
      +    mv $destRep $destRep.old 
      +    (($?)) && print -- "Echec du renommage (essai n°$essai)" >&2
      +    ((essai++))
      +done
      +
      +print -- "  renommage du repertoire tmp (nouveau) vers le standard"
      +print -P -- "  %BSite Indisponible%b $(date)"
      +essai=1
      +while [[ ! -e $destRep ]]; do
      +    mv $destRep.tmp $destRep
      +    (($?)) && print -P -- "%B[Site Indisponible]%b(essai n°$essai) Echec du renommage (mv $destRep.tmp $destRep)" >&2
      +    ((essai++))
      +done
      +
      +print -P -- "\t===\t%BSITE DISPONIBLE%b\t==="
      +
      +print -- "  renommage du repertoire old vers le tmp"
      +essai=1
      +while [[ ! -e $destRep ]]; do
      +    mv $destRep.old $destRep.tmp
      +    (($?)) && print -P -- "Echec du renommage n°$essai" >&2
      +    ((essai++))
      +done
      +
      +print -P -- "  publication terminée"
      +
      + +
      +
      #!/usr/bin/env zsh
      +
      +# Author: Yann Esposito
      +#   Mail: yann.esposito@gmail.com
      +# Synchronize with "mobileMe" iDisk account.
      +
      +mobileMeUser="firstname.lastname"
      +siteName="siteName"
      +
      +# Depending of my hostname the 
      +if [[ $(hostname) == 'ubuntu' ]]; then
      +    iDisk='/mnt/iDisk'
      +else
      +    iDisk="/Volumes/$mobileMeUser"
      +fi
      +
      +root=$HOME/Sites/$siteName
      +destRep=$iDisk/Web/Sites/$siteName
      +
      +[[ ! -d $root ]] && { 
      +    print -- "$root does not exist ; please verify the configuration ($0)" >&2; 
      +    exit 1 
      +}
      +
      +[[ ! -d $destRep ]] && { 
      +    print -- "$destRep does not exist, please mount the filesystem" >&2; 
      +    exit 1 
      +}
      +
      +if [[ $1 == '-h' ]]; then
      +    print -- "usage: $0:h [-h|-a|-s]"
      +    print -- "  -a sychronize primary index"
      +    print -- "  -h show this help"
      +    print -- "  -s only swap directories"
      +fi
      +
      +if [[ $1 == '-a' ]]; then
      +    print -- "Index synchronisation (${destRep:h})"
      +    rsync -av $root/index.html ${destRep:h}/index.html
      +fi
      +
      +print -- "Root = $root"
      +print -- "Dest = $destRep"
      +
      +if [[ ! $1 = '-s' ]]; then
      +    [[ ! -d $destRep.tmp ]] && mkdir $destRep.tmp
      +    print -P -- "%B[Sync => tmp]%b"
      +    result=1
      +    essai=1
      +    while (( $result > 0 )); do
      +        rsync -arv $root/Scratch/ $destRep.tmp
      +        result=$?
      +        if (( $result > 0 )); then
      +            print -P -- "%Brsync failed%b (try n°$essai)" >&2
      +        fi
      +        ((essai++))
      +    done
      +fi
      +
      +# SWAP
      +print -P -- "%B[Directory Swap (tmp <=> target)]%b"
      +essai=1
      +while [[ -e $destRep.old ]]; do
      +    print -n -- "remove $destRep.old"
      +    if ((essai>1)); then 
      +        print " (try n°$essai)"
      +    else
      +        print
      +    fi
      +    ((essai++))
      +    \rm -rf $destRep.old
      +done
      +
      +print -- "  renommage du repertoire sandard vers le .old"
      +essai=1
      +while [[ -e $destRep ]]; do
      +    mv $destRep $destRep.old 
      +    (($?)) && print -- "Failed to rename (try n°$essai)" >&2
      +    ((essai++))
      +done
      +
      +print -- "  renaming folder tmp (new) to the standard one"
      +print -P -- "  %BThe WebSite isn't working%b $(date)"
      +essai=1
      +while [[ ! -e $destRep ]]; do
      +    mv $destRep.tmp $destRep
      +    (($?)) && print -P -- "%B[WebSite not working]%b(try n°$essai) Failed to rename (mv $destRep.tmp $destRep)" >&2
      +    ((essai++))
      +done
      +
      +print -P -- "\t===\t%BWEBSITE SHOULD WORK NOW%b\t==="
      +
      +print -- "  rename old folder to tmp folder"
      +essai=1
      +while [[ ! -e $destRep ]]; do
      +    mv $destRep.old $destRep.tmp
      +    (($?)) && print -P -- "Failed to rename n°$essai" >&2
      +    ((essai++))
      +done
      +
      +print -P -- "  Publish terminated"
      +
      + + +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-09-11 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/11_Load_Disqus_Asynchronously/index.html b/Scratch/fr/blog/11_Load_Disqus_Asynchronously/index.html new file mode 100644 index 0000000..c3cf76a --- /dev/null +++ b/Scratch/fr/blog/11_Load_Disqus_Asynchronously/index.html @@ -0,0 +1,160 @@ + + + + + + YBlog - Load Disqus Asynchronously [en] + + + + + + + + + + + + + +
      + + +
      +

      Load Disqus Asynchronously [en]

      +
      +
      +
      +
      +

      Update

      +

      In fact this method works for old threads. But it fails to create new post threads. This is why I tried and be conquered by intensedebate, as you can see in the bottom of this page.

      +

      Remark I didn’t have any comment on my blog when I switched. Therefore my lack of influence was a good thing :-).

      +
      +

      Before begining, I must state that I love Disqus.

      +

      I know there is a similar blog entry at Trephine.org. Here I just add a straight and easy way to load disqus asynchronously using jQuery.

      +

      I also know there is a jQuery plugin to make just that. Unfortunately I had some issue with CSS.

      +

      Now let’s begin.

      +
      +

      Why?

      +

      Why should I want to load the disqus javascript asynchronously?

      +
        +
      • Efficiency: I don’t want my page to wait the complete execution of disqus script to load.
      • +
      • More independance: when disqus is down, my page is blocked!
      • +
      +
      +

      How?

      +

      I give a solution with jQuery, but I’m certain it will work with many other js library.

      +

      Javascript

      +

      replace:

      +
      +
      <script type="text/javascript" src="http://disqus.com/forums/YOUR_DISQUS_ID/embed.js"></script>
      +
      + +

      by

      +
      +
      window.disqus_no_style=true;
      +$(document).ready(function(){
      +    $.getScript("http://disqus.com/forums/YOUR_DISQUS_ID/embed.js");
      +});
      +
      + +

      If you forget the window.disqus_no_style=true; then your page will be blank. Simply because without this option, the javascript use a document.write action after the document was closed, which cause a complete erasing of it.

      +

      CSS

      +

      But with this option you still need to provide a CSS. This is why you have to copy the css code from the embed.js file and rewrite it in a CSS file. You can download the CSS I obtained.

      +
      +

      Now it’s done. I believe all should be fine but I just finished the manip for my own site only 1 hour ago. Therefore there should be some error, tell me if it is the case.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-09-17 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/index.html b/Scratch/fr/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/index.html new file mode 100644 index 0000000..e40289f --- /dev/null +++ b/Scratch/fr/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/index.html @@ -0,0 +1,147 @@ + + + + + + YBlog - Disqus contre Intense Debate (pourquois j'ai changé) + + + + + + + + + + + + + +
      + + +
      +

      Disqus contre Intense Debate (pourquois j'ai changé)

      +
      +
      +
      +
      +

      Disqus vs. Intense Debate

      +

      J’ai écrit un article sur la façon dont j’ai essayé d’intégrer Disqus. Mon problème majeur avec Disqus c’était que ma page ne s’affichait pas correctement tant que les commentaire n’avait pas fini de s’afficher. Ça m’est arrivé plusieurs fois d’avoir ma page complètement bloquée parce que les serveurs de Disqus ne répondait pas. C’est pourquoi j’ai essayer de l’inclure de manière asynchrone. Cependant j’ai eu des difficultés pour le faire fonctionner correctement.

      +

      De plus il n’a pas été trivial de faire en sorte que les commentaires soient commun à plusieurs pages différentes (chaque page à trois représentations différentes, une par language plus une version multi-langue).

      +

      Je dois reconnaître que je suis un peu triste de quitter Disqus parce que pour chacun de mes problèmes giannii m’a aidé du mieux qu’il a pu. Cependant les problèmes que j’ai eu étaient inhérents à des choix de conceptions plus que de simples petits problèmes techniques.

      +

      Lorsque j’ai commencé à intégrer Disqus je n’ai jamais essayé Intense Debate. Maintenant que j’ai essayé je doit dire que je suis conquis. Il correspond exactement à ce que j’espérais de ce type de service.

      +

      Pour le rendre complètement asynchrone il suffit de récupérer leur js commun et de remplacer la ligne suivante :

      +
      +
      document.getElementsByTagName("head")[0].appendChild(commentScript);
      +
      + +

      par (si vous utilisez jQuery) :

      +
      +
      $(document).ready( function() {
      +    document.getElementsByTagName("head")[0].appendChild(commentScript);
      +});
      +
      + +

      And the Winner is: Intense Debate

      +

      Pour conclure les avantages majeurs (pour moi) d’Intense Debate par rapport à Disqus:

      +
        +
      • Se charge de façon asynchrone ; ne bloque pas mon site web
      • +
      • Permet d’ajouter sans rien de plus des boutons comme “share to any” et les charge eux aussi de façon asynchrone.
      • +
      +

      Voilà.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-09-28 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2009-09-jQuery-Tag-Cloud/index.html b/Scratch/fr/blog/2009-09-jQuery-Tag-Cloud/index.html new file mode 100644 index 0000000..4afb857 --- /dev/null +++ b/Scratch/fr/blog/2009-09-jQuery-Tag-Cloud/index.html @@ -0,0 +1,316 @@ + + + + + + YBlog - jQuery Tag Cloud [en] + + + + + + + + + + + + + +
      + + +
      +

      jQuery Tag Cloud [en]

      +
      +
      +
      +
      +

      Here is how I done the tag cloud of my blog. It is done mostly in jQuery. All my site is static and pages are generated with nanoc. It is (in my humble opinion) the modern geek way to make a website.

      +

      This is why I’ll give only a Ruby Generator, not a full javascript generator. But you can easily translate from Ruby to Javascript.

      +

      Here is what you should obtain:

      +
      +
      +<%= tagCloud %> +
      + +
      +

      jQuery

      +

      Here is the simple jQuery code:

      +
      +
          $(document).ready( function(){$('.list').hide();} );
      +    function tagSelected(id) {
      +        $('.list').hide();
      +        $('#'+id).fadeIn();
      +        $('.tag.selected').removeClass('selected');
      +        $('#tag_'+id).addClass('selected');
      +    }
      +
      + +

      This code will hide all the div containing links to articles containing the tag. And create a function do show the div containing the tag.

      +

      For each tag I create a span element:

      +
      +
          <span   style="font-size: 1.0em;" 
      +            class="tag" 
      +            onClick="tagSelected('[TAG]')" 
      +            id="tag_[TAG]">
      +        [TAG]
      +    </span> 
      +
      + +

      and a div containing links associtated to this tag:

      +
      +
          <div id="[TAG]">
      +        <h4>[TAG]</h4>
      +        <ul>
      +            <li> LINK 1 </li>
      +            <li> LINK 2 </li>
      +        </ul>
      +    </div> 
      +
      + +
      +

      nanoc

      +

      Here is how I generate this using nanoc 2.

      +

      If you want to make it fully jQuery one, it shouldn’t be too difficult, to use my ruby code and translate it into javascript.

      +

      In a first time tags correpond of the list of all tags.

      +
      +
      def tags
      +    return @page.tags.join(', ')
      +end
      +
      + +

      A function to create a data structure associating to each tag its occurence.

      +
      +
      # generate an hash tag => number of occurence of tag
      +def tagNumber
      +    tags={}
      +    @pages.each do |p|
      +        if p.tags.nil?
      +            next
      +        end
      +        p.tags.each do |t|
      +            if tags[t]
      +                tags[t]+=1
      +            else
      +                tags[t]=1
      +            end
      +        end
      +    end
      +    return tags
      +end
      +
      + +

      I also need a data structure who associate to each tag a list of pages (at least url and title).

      +
      +
      # generate an hash tag => [ page1, page2 ... ]
      +def tagRefs
      +    tagLinks={}
      +    @pages.each do |p|
      +        if p.tags.nil?
      +            next
      +        end
      +        p.tags.each do |t|
      +            if tagLinks[t].nil?
      +                tagLinks[t]=[ p ]
      +            else
      +                tagLinks[t] <<= p
      +            end
      +        end
      +    end
      +    return tagLinks
      +end
      +
      + +

      Calculate the real size of each tag to be displayed.

      +

      I choosen not to use the full range of size for all the tag. Because if no tag has more than n (here 10) occurences, then it doesn’t deserve to be of the maximal size.

      +
      +
      def tagRealSize
      +    tags=tagNumber
      +    max=tags.values.max
      +    min=tags.values.min
      +    # size in CSS em.
      +    minSize=1.0
      +    maxSize=2.5
      +    tagSize={}
      +    tags.each do |t,n|
      +        if ( max == min )
      +            tagSize[t]=minSize
      +        else
      +            # normalized value between 0 and 1
      +            # if not tag appear more than 10 times, 
      +            # then it cannot have the maximal size
      +            tagSize[t]=[ ( n - min + 0.0 ) / ( max - min ) , 
      +                         (n - min) / 10.0 ].min
      +            # from normalized size to real size
      +            tagSize[t]=( tagSize[t] ) * (maxSize - minSize) + minSize
      +        end
      +    end
      +    return tagSize
      +end
      +
      + +

      Finaly a function to generate the XHTML/jQuery code

      +
      +
      # generate an XHTML/jQuery code for tag cloud
      +def tagCloud
      +    tagLinks=tagRefs
      +    tagSize=tagRealSize
      +
      +    # begin to write the code
      +    tagCloud=%{<script type="text/javascript">
      +        $(document).ready( function(){$('.list').hide();} );
      +        function tagSelected(id) {
      +            $('.list').hide();
      +            $('#'+id).fadeIn();
      +            $('.tag.selected').removeClass('selected');
      +            $('#tag_'+id).addClass('selected');
      +        }
      +    </script><div id="tagcloud">}
      +    # Creation of the tags <span>
      +    tagSize.sort{|a,b| a[0].downcase <=> b[0].downcase}.each do |t,s|
      +        tag_in_id=t.gsub(/\W/,'_')
      +        # HTML protected version of the tag
      +        # for example, replace ' ' by '&nbsp;'
      +        protected=t.gsub(/&/,'&amp;').gsub(/ /,'&nbsp;').gsub(/</,'&lt;').gsub(/>/,'&gt;')
      +        tagCloud <<= %{
      +            <span style="font-size: #{s}em;" 
      +                  class="tag" 
      +                  onClick="tagSelected('#{tag_in_id}')" 
      +                  id="tag_#{tag_in_id}">
      +                #{protected}
      +            </span> }
      +    end
      +    tagCloud <<= %{</div><div id="hiddenDivs" >}
      +    # Creation of the divs containing links associated to a tag.
      +    tagLinks.each do |t,l|
      +        tag_in_id=t.gsub(/\W/,'_')
      +        tagCloud <<= %{
      +            <div id="#{tag_in_id}" class="list">
      +                <h4>#{t}</h4><ul>}
      +        # generate the link list
      +        l.each do |p|
      +            tagCloud <<= %{<li><a href="#{p.path}">#{p.title}</a></li>}
      +        end
      +        tagCloud <<= %{</ul></div>}
      +    end
      +    tagCloud <<= %{</div>}
      +    return tagCloud # yeah I know it is not necessary
      +end
      +
      + +

      You can download the complete file to put in your ‘lib’ directory. Beware, it is a nanoc 2 version, you’ll have to make some small changes like replace @pages by @items to be nanoc3 compatible.

      +

      Of course to be nice you need the associated CSS

      +
      +
      
      +// Change the color when mouse over
      +.tag:hover {
      +  color: #cc0000; }
      +
      +// Change the color when tag selected
      +.tag.selected {
      +  color: #6c0000; }
      +
      +// a bit of space and pointer cursor
      +.tag {
      +  cursor: pointer;
      +  margin-left: .5em;
      +  margin-right: .5em; }
      +
      + +

      That’s all folks.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-09-23 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2009-09-replace-all-except-some-part/index.html b/Scratch/fr/blog/2009-09-replace-all-except-some-part/index.html new file mode 100644 index 0000000..82d698b --- /dev/null +++ b/Scratch/fr/blog/2009-09-replace-all-except-some-part/index.html @@ -0,0 +1,191 @@ + + + + + + YBlog - Remplacer tout sauf une partie + + + + + + + + + + + + + +
      + + +
      +

      Remplacer tout sauf une partie

      +
      +
      +
      +
      +

      My problem is simple:

      +

      I want to filter a text except some part of it. I can match easily the part I don’t want to be filtered. For example

      +
      +
      ...
      +text
      +...
      +BEGIN not to filter
      +...
      +text
      +...
      +END not to filter
      +...
      +text
      +...
      +
      + +

      I searched a better way to do that, but the best I can do is using split and scan.

      +
      +
      def allExceptCode( f, content )
      +    # Beware the behaviour will change if you add
      +    # parenthesis (groups) to the regexp!
      +    regexp=/<code[^>]*>.*?<\/code>|<pre[^>]*>.*?<\/pre>/m
      +    tmp=""
      +    mem=[]
      +    content.scan(regexp).each do |c|
      +        mem <<= c
      +    end
      +    i=0
      +    content.split(regexp).each do |x|
      +        tmp <<= send(f,x) 
      +        if not mem[i].nil? 
      +            tmp <<= mem[i]
      +            i+=1
      +        end
      +    end
      +    tmp
      +end
      +
      + +

      An usage is:

      +
      +
      def filter(content)
      +    content.gsub(/e/,'X')
      +end
      +...
      +allExceptCode(:filter, content)
      +...
      +
      + +

      A better syntax would be:

      +
      +
      # !!!!!!!!!! THIS SYNTAX DOES NOT WORK !!!!!!! #
      +def allExceptCode( f, content )
      +    regexp=/<code[^>]*>.*?<\/code>/m
      +    tmp=""
      +    content.split(regexp).each do |x|
      +        separator=$&
      +        tmp <<= send(f,x) 
      +        if not separator.nil?
      +            tmp <<= separator
      +        end
      +    end
      +    tmp
      +end
      +
      + +

      I would expect the split make a search on a regular expression and then give the matched expression into the $& variable. But it is not the case.

      +

      If someone know a nicer way to do that I will be happy to know how.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-09-22 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/index.html b/Scratch/fr/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/index.html new file mode 100644 index 0000000..5a80fa1 --- /dev/null +++ b/Scratch/fr/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/index.html @@ -0,0 +1,204 @@ + + + + + + YBlog - Synchronisation avec mobileme (2) + + + + + + + + + + + + + +
      + + +
      +

      Synchronisation avec mobileme (2)

      +
      +
      +
      +
      +

      J’ai déjà discuté de la façon dont je synchronise mon site web sur mobileme. J’ai amélioré mon script pour le rendre incrémental.

      +

      Voici mon script, il créé tout d’abord un fichier qui contient la liste des fichiers avec leur hash. Afin de les comparer avec ceux qui sont en ligne sans avoir à les parcourir. Ensuite pour chaque fichier qui semble différent, je met à jour le contenu.

      +

      Cependant même avec ce script j’ai encore des problèmes. Dû à webdav. En particulier le renommage de répertoire. Par exemple :

      +
      + mv folder folder2 +
      + +

      Retourne OK et pourtant :

      +
      + $ ls folder folder2 +
      + +

      Bouuhh…

      +

      Pour résoudre ce type de problèmes j’utilise un framework en zsh. Il résout presque tous les problèmes liés à webdav à l’exception du renommage de répertoire.

      +
      +

      #!/usr/bin/env zsh

      +

      function samelineprint { print -n -P – “$*” }

      +

      avec 1 essai par seconde: 300 = 5 minutes

      +

      maxessais=300

      +

      try to create a directory until success

      +

      function trymkdir { target=“$1" print -- mkdir -p $target local essai=1 while ! mkdir -p $target; do samelineprint "Echec: essai n°$essai” ((essai++)) ((essai>maxessais)) && exit 5 done print }

      +

      try to copy until success

      +

      function trycp { element=“$1” target=“$2" if [[ ! -d ${target:h} ]]; then trymkdir target: hfilocalessai = 1print −  − cpelement $target while ! \cp $element $target; do samelineprint "Echec: essai n°$essai” ((essai++)) ((essai>maxessais)) && exit 5 done print }

      +

      try to remove until success

      +

      function tryrm { target=“$1" local essai=1 local options='' [[ -d $target ]] && options=‘-rf’ print – rm optionstarget while ! rm optionstarget; do samelineprint”Echec: essai n°$essai" ((essai++)) ((essai>maxessais)) && exit 5 done essai=1 while [[ -e $element ]]; do samelineprint “rm reussi mais fichier source non disparu n°$essai” sleep 1 ((essai++)) ((essai>maxessais)) && exit 5 done print }

      +

      try to rename until success

      +

      function tryrename { element=“$1” target=“$2" local essai=1 while [[ -e $target ]]; do samelineprint”Echec n°essailefichiertarget existe déjà" ((essai++)) ((essai>maxessais)) && exit 5 sleep 1 done print – mv elementtarget while ! mv elementtarget; do samelineprint “Echec: essai n°$essai" ((essai++)) ((essai>maxessais)) && exit 4 done essai=1 while [[ -e $element ]]; do samelineprint”mv reussi mais fichier source non disparu n°$essai" sleep 1 ((essai++)) ((essai>maxessais)) && exit 5 done print }

      +

      try to move until success

      +function trymv { element=“$1” target=“$2" local essai=1 print -- mv $element targetwhile!mvelement $target; do samelineprint "Echec: essai n°$essai” ((essai++)) ((essai>maxessais)) && exit 5 done essai=1 while [[ -e $element ]]; do samelineprint “mv reussi mais fichier source non disparu n°$essai” sleep 1 ((essai++)) ((essai>maxessais)) && exit 5 done print }
      +
      + +

      Et voici le code qui me permet de synchroniser mon site web. Il y a une partie un peu incompréhensible. C’est pour enlever les mail réencodés par le filtre bluecloth qui est une implémentation de markdown. Mes mails, sont encodés à chaque fois de façon différente à chaque réengendrement de page html. C’est pourquoi je les enlève pour ne pas les uploadés inutilement à chaque fois.

      +
      +

      #!/usr/bin/env zsh

      +

      Script synchronisant le site sur me.com

      +

      normalement, le site est indisponible le moins de temps possible

      +

      le temps de deux renommages de répertoire

      +

      get configuration

      +

      mostly directories

      +

      source $0:h/config

      +

      get trycp function (copy until success)

      +

      source $0:h/webdav-framework

      +

      if [[ $1 == ‘-h’ ]]; then print – “usage : $0:h [-h|-s|-d]” print – " -a sychronise aussi l’index" print – " -h affiche l’aide" print – " -d modification directe (pas de swap)" print – " -s swappe simplement les répertoires" fi

      +

      publication incrementale

      +

      function incrementalPublish { local ydestRep=destRepsuffix localRef=“$srcRep/map.yrf" print -- "Creation du fichier de references" create-reference-file.sh > $localRef remoteRef=”/tmp/remoteSiteMapRef.$$.yrf" if [[ ! -e "$ydestRep/map.yrf" ]]; then # pas de fichier de reference sur la cible print – “pas de fichier de reference sur la cible, passage en mode rsync” rsyncPublish swap else trycp “$ydestRep/map.yrf" "$remoteRef” typeset -U filesToUpdate filesToUpdate=( (difflocalRef $remoteRef | awk ’/1/ {print $2}' ) ) if ((${#filesToUpdate} == 1)); then print – “Seul le fichier ${filesToUpdate} sera téléversé" elif ((${#filesToUpdate}<10)); then print –”${#filesToUpdate} fichiers seront téléversés :" print -- "${filesToUpdate}" else print – "${#filesToUpdate} fichiers seront téléversés" fi # copy all file with some differences # except the map in case of error for element in $filesToUpdate; do if [[ $element == “/map.yrf” ]]; then continue fi if [[ -e srcRepelement ]]; then trycp srcRepelement ydestRepelement else tryrm ydestRepelement fi done # if all went fine, copy the map file trycp srcRep / map. yrfydestRep/map.yrf # remove the temporary file }

      +

      publication via rsync

      +

      function rsyncPublish { result=1 essai=1 while (( result > 0)); doprint −  − rsync − arvsrcRep/ destRep. tmpif((!testmode)); thenrsync − arvsrcRep/ destRep. tmpfiresult = ? if (( $result > 0 )); then print -P -- "%BEchec du rsync%b (essai n°$essai)" >&2 fi ((essai++)) done }

      +

      swap

      +

      function swap { print -P – “%B[Directory Swap (tmp <=> target)]%b” [[ -e $destRep.old ]] && tryrm $destRep.old

      +
      print -- "  renommage du repertoire sandard vers le .old"
      +tryrename $destRep $destRep.old 
      +
      +print -- "  renommage du repertoire tmp (nouveau) vers le standard"
      +print -P -- "%B[Site Indisponible]%b $(date)"
      +tryrename $destRep.tmp $destRep
      +print -P -- "%B[Site Disponible]%b $(date)"
      +
      +print -- "  renommage du repertoire old vers le tmp"
      +tryrename $destRep.old $destRep.tmp
      +
      +print -P -- "  publication terminée"
      +

      }

      +

      print – “Root = $webroot" print -- "Dest = $destRep”

      +

      if [[ “$1” = “-s” ]]; then swap else print -P “Copie de l’init” -f webroot / Scratch / multi / index. htmlwebroot/index.html

      +
      if [[ "$1" = "-d" ]]; then
      +    suffix=""
      +else
      +    suffix=".tmp"
      +fi
      +print -P -- "%BSync%b[${Root:t} => ${destRep:t}$suffix]"
      +incrementalPublish
      +fi
      +
      + +

      C’est ma façon de remplacer rsync avec des filesystem qui ne permettent pas de l’utiliser. J’espère que ça pourra vous être utile. Je serai heureux de savoir si quelqu’un à une idée sur comment gérer le problème de renommage de répertoire avec webdav.

      +
      +
      +
        +
      1. <>

      2. +
      +
      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-10-28 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2009-10-30-How-to-handle-evil-IE/index.html b/Scratch/fr/blog/2009-10-30-How-to-handle-evil-IE/index.html new file mode 100644 index 0000000..1d5bb62 --- /dev/null +++ b/Scratch/fr/blog/2009-10-30-How-to-handle-evil-IE/index.html @@ -0,0 +1,144 @@ + + + + + + YBlog - Une CSS pour IE seulement + + + + + + + + + + + + + +
      + + +
      +

      Une CSS pour IE seulement

      +
      +
      +
      +
      +

      Pour les développeur de site web Internet Explorer est un cauchemar. C’est pourquoi j’utilise un style complètement différent pour ce navigateur. Avec la librairie jQuery.

      +
      + $(document).ready( function() { if ($.browser[“msie”]) { // include the ie.js file $(‘head’).append(’ + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-10-30 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2009-10-Focus-vs-Minimalism/index.html b/Scratch/fr/blog/2009-10-Focus-vs-Minimalism/index.html new file mode 100644 index 0000000..f305966 --- /dev/null +++ b/Scratch/fr/blog/2009-10-Focus-vs-Minimalism/index.html @@ -0,0 +1,163 @@ + + + + + + YBlog - <em>Focus</em> > Minimalisme + + + + + + + + + + + + + +
      + + +
      +

      Focus > Minimalisme

      +
      +
      +
      +
      +

      Je crois que le but du minimalisme est de facilité le Focus c’est-à-dire la concentration sur le contenu. Je crois que le minimalisme doit être un moyen et pas une fin. Le Focus devrait être le but, et je pense que le minimalisme n’est pas obligatoire pour l’atteindre.

      +

      C’est pourquoi mon design n’est pas minimaliste. Mais j’ai décidé d’enlever la majorité des objets servant à la navigation pour améliorer l’attention sur l’article. Peut-être que plus tard, je préfèrerai laisser le menu dans les pages normales du site pour ne le cacher que dans les articles de blog. Pour l’instant je le cache partout.

      +
      +

      Détails techniques

      +

      Pour ceux qui souhaitent connaître les détails techniques derrière le menu apparaissant/disparaissant, voici le code utilisant jQuery.

      +

      L’HTML :

      +
      +
      <div id="menuButton"></div>
      +<div id="entete">#content of the menu</div>
      +
      + +

      La CSS :

      +
      +

      #menuButton { font-size: 2em; height: 2em; line-height: 1.8em; width: 2em; position: fixed; left: 0; top: 0; z-index: 9001 }

      +

      menuButton:hover {

      +

      cursor: pointer; }

      +

      entete {

      +top: 5em; left: 0; position: fixed; width: 10em; z-index: 9000; } ~~~~~~ +
      + +

      Le code javascript (utilisant jQuery)

      +
      +
      function hideMenu() {
      +    $('#entete').animate({left:"-10em"}, 500 );
      +    $('#menuButton').html('&rarr;');
      +}
      +function showMenu() {
      +    $('#entete').animate({left:"0em"}, 500 );
      +    $('#menuButton').html('&larr;');
      +}
      +function toggleMenu() {
      +    if ( $('#entete').css('left')=='-10em' ) {
      +        showMenu();
      +    } else {
      +        hideMenu();
      +    }
      +}
      +
      + +

      Le résultat est visible dans le coin en haut à droite de cet article.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-10-22 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2009-10-How-to-preload-your-site-with-style/index.html b/Scratch/fr/blog/2009-10-How-to-preload-your-site-with-style/index.html new file mode 100644 index 0000000..bac6372 --- /dev/null +++ b/Scratch/fr/blog/2009-10-How-to-preload-your-site-with-style/index.html @@ -0,0 +1,192 @@ + + + + + + YBlog - Charger une page web avec style + + + + + + + + + + + + + +
      + + +
      +

      Charger une page web avec style

      +
      +
      +
      +
      +

      Exemple

      +

      Voici comment apparaissent mes pages pendant leur chargement.

      +
      +

      +Voilà ! Je suis chargée ! +

      +

      +Cliquez-moi dessus pour recommencer. +

      +
      + +Loading… loading logo +
      + +
      + +

      J’ai d’abord essayé d’intégrer queryLoader, mais il ne comblait pas mes besoins.

      +

      Ce plugin ajoutait un ‘div’ noir pour cacher le contenu du site. Cependant, comme le script doit être lancé à la fin du code source. Pendant un petit moment, on peut voir mon site en train de se mettre à jour.

      +

      Pour cacher ce petit ‘artefact’, voici comment je m’y suis pris.

      +

      Code

      +

      D’abort il faut ajouter tout en haut du body cette fois un div qui va être le voile noir qui va tout cacher.

      +
      +
      ...
      +<body>
      +<div id="blackpage">
      +    content to display during the loading.
      +</div>
      +...
      +
      + +

      et le CSS correspondant au div #blackpage :

      +
      +
      #blackpage
      +  top: 0 
      +  left: 0 
      +  width: 100%
      +  height: 100%
      +  margin-left: 0
      +  margin-right: 0
      +  margin-top: 0
      +  margin-bottom: 0
      +  position: absolute
      +  text-align: center
      +  color: #666
      +  padding-top: 10em
      +  background-color: #eee
      +  z-index: 9000
      +
      + +

      ainsi que le code jQuery associé :

      +
      +
      $(document).ready(function(){
      +    $('#blackpage').fadeOut();
      +});
      +
      + +

      Oui, c’est aussi simple que ça. Maintenant ajouter le #blackpage tout en haut de ma page me permet d’être certain de tout cacher pendant le chargement de la page.

      +

      J’espère que ça a pu vous être utile !

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-10-03 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2009-10-Wait-to-hide-a-menu-in-jQuery/index.html b/Scratch/fr/blog/2009-10-Wait-to-hide-a-menu-in-jQuery/index.html new file mode 100644 index 0000000..6f53c01 --- /dev/null +++ b/Scratch/fr/blog/2009-10-Wait-to-hide-a-menu-in-jQuery/index.html @@ -0,0 +1,170 @@ + + + + + + YBlog - Un menu qui attends avant de se cacher + + + + + + + + + + + + + +
      + + +
      +

      Un menu qui attends avant de se cacher

      +
      +
      +
      +
      +

      J’ai déjà dit pourquoi je préférais que mon menu de navigation soit caché. J’ai finalement décidé d’attendre un peu avant de cacher le menu. Juste le temps que l’utilisateur le voit. Mais voilà. Comment faire pour qu’il ne disparaisse que lorsque l’on ne s’en sert pas pendant un petit moment ?

      +

      Voici la solution que j’utilise avec jQuery

      +

      HTML :

      +
      +
          <div id="menuButton"></div>
      +    <div id="entete">
      +        <ul>
      +            <li> menu item 1 </li>
      +            ...
      +            <li> menu item n </li>
      +        </ul>
      +    </div>
      +
      + +

      CSS :

      +
      +

      #entete { top: 1em; left: 0; position: fixed; width: 10em; z-index: 2000; }

      +
      #entete {
      +  top: 1em;
      +  height: 22em;
      +  left: 0;
      +  position: fixed;
      +  width: 10em; }
      +
      +
      + +

      Javascript :

      +
      +

      var last=0;

      +

      // will hide the menu in 5 seconds // if the variable ‘last’ has not changed its value function autoHideMenu(value) { setTimeout(function(){ if ( last == value ) { hideMenu(); } },5000); }

      +

      $(document).ready( function() { // show the menu when the mouse is on // the good area $(‘#menuButton’).hover(showMenu);

      +
      // If the mouse is on the menu change the
      +// value of 'last'
      +// try to hide the menu when the mouse 
      +// go out off the menu.
      +$('#entete').hover(
      +    function(){last+=1;}, 
      +    function(){autoHideMenu(last);} );
      +autoHideMenu(0);
      +

      });

      +

      // show / hide menu functions details

      +

      // move to the left function hideMenu() { $(‘#entete’).animate({left:“-10em”}, 500 ); }

      +

      // move to right and will try to hide in 5 sec. function showMenu() { $(‘#entete’).animate({left:“0em”}, 500 ); last+=1; autoHideMenu(last); }

      +
      +
      + +

      Simple et peu gourmand en ressources. Pas de timer (ou presque), pas de fuite de mémoire, pas d’utilisation de date…

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-10-26 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2009-10-launch-daemon-from-command-line/index.html b/Scratch/fr/blog/2009-10-launch-daemon-from-command-line/index.html new file mode 100644 index 0000000..2825d08 --- /dev/null +++ b/Scratch/fr/blog/2009-10-launch-daemon-from-command-line/index.html @@ -0,0 +1,130 @@ + + + + + + YBlog - lancer un démon en ligne de commande + + + + + + + + + + + + + +
      + + +
      +

      lancer un démon en ligne de commande

      +
      +
      +
      +
      +

      Une petite astuce dont je ne me souvient jamais (je ne sais pas pourquoi).

      +

      Lorsque que vous souhaitez lancer une commande qui ne soit pas tuée après la fermeture du terminal voici comment s’y prendre :

      +
      + nohup cmd & ~~~~~~ cmd est la commande que vous souhaitez lancer. +
      + +

      Je laisse cette astuce ici pour moi et dans l’espoir que ça pourra aussi être utile à quelqu’un d’autre.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-10-23 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2009-10-untaught-git-usage/index.html b/Scratch/fr/blog/2009-10-untaught-git-usage/index.html new file mode 100644 index 0000000..71626a7 --- /dev/null +++ b/Scratch/fr/blog/2009-10-untaught-git-usage/index.html @@ -0,0 +1,258 @@ + + + + + + YBlog - Usages non dits de Git + + + + + + + + + + + + + +
      + + +
      +

      Usages non dits de Git

      +
      +
      +
      +
      +

      Je décris pourquoi j’ai eu tant de mal à me faire à Git. Il y a en effet une partie “non dite” qui m’a bloqué pendant un bon moment. Jusqu’à ce que je découvre le bon document.

      +

      Le fait est que les branches légères ne sont pas destinée à être des branches isolées. Ainsi, il y a un “workflow standard” qui s’il n’est pas suivi rend l’utilisation de Git inappropriée.

      +
      +

      La décentralisation en action

      +

      De SVN à Bazaar

      +

      J’étais un fervent utilisateur de subversion (svn). Lorsqu’un jour, comme beaucoup de gens je découvris ce qu’en pensais Linus Torvald sur une vidéo. Ventant les mérites d’un système de versions concurrentes décentralisé.

      +

      En effet une fois qu’on s’y intéresse un peu, on voit tous les avantages pratiques qu’apporteraient en théorie un tel système.

      +

      J’ai alors eu besoin d’un système de version dans mon équipe. Ils n’étaient pas familier avec les systèmes de versions. À par ceux possédant une GUI, qui sont lourds et administrés par un responsable.

      +

      Après quelques recherches trois choix se dessinent :

      + +

      En me renseignant un peu sur les forums et en essayant les trois, je me suis vite rendu compte que celui possédant l’interface utilisateur la plus simple était Bazaar*. Mon choix était fait.

      +

      De bazaar à Git

      +

      Je me suis alors familiarisé avec Bazaar. Et je dois dire que c’était vraiment naturel en venant de subversion. La commande pull correspond au update, la commande push correspond au commit. Puis les commandes commit et update existent toujours si on en a besoin et qu’on veut utiliser un workflow identique à celui de subversion.

      +

      Mais plus le temps passe et plus de partout sur les blog, c’est surtout Git qui a le vent en poupe.

      +

      Je décide alors d’utiliser Git en particulier pour versionner le site que vous êtes en train de lire. Sauf que je le trouve vraiment difficile d’utilisation et surtout complètement contre intuitif (j’y reviendrai plus tard).

      +

      Alors que j’essaye de trouver de l’aide et que je dis qu’il est plus difficile à utiliser que Bazaar, beaucoup me répliquent que c’est :

      +
      +

      Super-tellement-trop-simple que même ma fille de 12 ans qui n’y comprend rien en informatique l’utilise pour versionner ses documents. Elle s’en sert très facilement en créant des branches et tout et tout…

      +
      +

      Bon alors si une gamine 12 ans trouve ça très naturel et que moi (avec mon Doctorat en informatique) j’ai du mal à faire ce que je veux, c’est un peu frustrant et humiliant. Mais qu’est-ce qui fait que Git est naturel aux uns (comme pour CocoaSamurai ) et très confus pour moi ?

      +

      C’est en lisant un article j’ai enfin compris ce qu’il me manquait. C’est la partie non dite de la conception. Celle que tous les développeurs et les concepteurs trouvaient comme aller de soi. Sauf que pour moi, ce n’était pas du tout le cas.

      +

      - Je parle de ClearCase(c). Et oui, je sais qu’il existe des commandes en lignes pour ClearCase(c), mais ce n’est pas comme ça qu’ils étaient habitués à travailler avec des systèmes de “versionning”.

      +

      * - Je n’ai pas vraiment donné sa chance à Mercurial, la terminologie qu’ils utilisaient était trop éloignée de celle de svn à laquelle je m’étais habituée.

      +
      +

      Lorsqu’on voit les présentations autour de la notion de branche et de dcvs, on s’imagine dans un monde où chaque branche est totalement isolée des autres sauf au moment de “merger” les différences les unes des autres. Tout est magique. C’est la façon de voir “Mondes Parallèles”. Cette façon de voir est expliquée dans le très bon article sur les branches sur betterexplained.

      +

      Sauf que les concepteurs de Git (conçu pour le noyau Linux) ont plutôt imaginé un système basé non pas autour des mondes parallèles, mais sur la notion de Patch.

      +

      D’un côté Mondes Parallèles, de l’autre Patchs. Il y a beaucoup de notions équivalentes dans les deux cas, mais aussi quelques différences.

      +
        +
      • Bazaar est complètement basé sur la notion de Mondes Parallèles qui va impliquer un phénomène de Patch.
      • +
      • Alors que Git est basé sur la notion de Patch qui va impliquer la création de Mondes Parallèles.
      • +
      +

      Je ne vais pas argumenté pour savoir si une façon de voir est meilleure que l’autre. Disons simplement que ma façon d’entrer dans l’explication des DCVS était par le biais des Mondes Parallèles alors que Git est conçut selon l’autre notion.

      +

      De la théorie à la pratique

      +

      Bien que je pense avoir bien compris les mécanismes conceptuels de Git, la mise en pratique posait problème. Et le point noir, celui qui m’empêchait de comprendre Git comme je le souhaitais était dû à la notion de branche légère.

      +

      Une branche légère qu’est-ce que c’est me demanderez-vous ? Si comme moi on vient de Bazaar, c’est une notion complètement nouvelle. Il s’agit simplement de la capacité de créer une nouvelle branche en réutilisant le répertoire dans lequel on se trouve.

      +

      En pratique pour changer de branche, il faut lancer une commande. Tous les fichiers locaux non modifiés depuis le dernier commit seront alors modifiés pour correspondre à la version de la branche.

      +

      En théorie, les branches légères sont des branches tout comme avec bazaar. D’ailleurs le mot utilisé n’est pas branche légère mais branche tout court.

      +

      Sauf que contrairement à une branche standard résidant dans son propre répertoire, une branche légère est destinée à n’être qu’un patch de la branche principale du répertoire dans lequel elle réside.

      +

      Bien entendu on pourra m’objecter que l’on peut tout à fait utiliser ces branches légères comme des branches normales. Mais elles n’ont pas été conçues pour ça. Et donc, en pratique, c’est gênant de les utiliser de la sorte.

      +

      Voici comment Git est censé être utilisé (pour plus de détails vous pouvez lire Git for Designers en anglais) :

      +
        +
      • récupération ou création d’un “repository” central Le Grand Repository
      • +
      • Création d’un branche légère locale qui contient les différences qui vont devoir être “patché” dans LE GRAND REPOSITORY.
      • +
      +

      Voici comment n’est pas censé être utilisé Git :

      +
        +
      • Récupération ou création d’un “repository” quelconque
      • +
      • Création d’un branche légère locale qui n’a pas pour vocation de mettre à jour le “repository” d’origine, mais de vivre sa vie de façon autonome et de récupérer les mises à jour des autres du “repository” d’origine.
      • +
      +

      En effet cette petite notion m’a empêché de fonctionner correctement.

      +

      En pratique

      +

      Maintenant que j’ai compris ça, je peux enfin comprendre pourquoi Git a tant de défenseurs qui continue de trouver que Git est meilleur que les autres.

      +

      La notion de branche légère est essentielle à Git et vraiment utile en pratique. Notamment pour la gestion de ce site. Par contre, elle m’empêche d’utiliser les branches comme je le souhaiterai.

      +

      Mais dans ce cas-là, je n’ai qu’à utiliser des clônes et pas des branches légères.

      +

      Des exemples

      +

      Je trouve toujours que les terminologies de bazaar sont plus claires et plus concises.

      +
      +bzr revert +
      + +

      est quand même plus clair que

      +
      +git reset –hard HEAD +
      + +

      De la même façon

      +
      +bzr revert -r -3 +
      + +

      je trouve ça mieux que

      +
      +git reset –hard HEAD~3 +
      + +

      Là ça va commencer à se compliquer. Si on veut revenir dans le temps sur toute l’arborescence, avec Git on utilise reset.

      +
      +OK +
      + +

      Maintenant si je veux revenir dans le temps sur un seul fichier. Naturellement on se dit :

      +
      +git reset –hard FILE +
      + +
      +ET BIEN NON ! +
      + +

      La solution c’est :

      +
      +git checkout FILE +
      + +

      Quoi ? checkout !? Bon, d’accord, j’accepte, pourquoi pas après tout ? En plus quand on est habitué à Bazaar c’est :

      +
      +git revert FILE +
      + +

      Ce que je trouve quand même bien plus naturel.

      +

      Mais là où ça devient vraiment difficile de s’y faire c’est pour changer de branche.
      Avec Bazaar ça donne :

      +
      +cd ../branch +
      + +

      Bon ok, il faut changer de répertoire, un répertoire par branche. Ça consomme de l’espace disque mais au moins on voit où on est. Avec Git voilà comment on change de branche (branche légère) :

      +
      +git checkout branch +
      + +

      Alors là, on se dit “WTF?” ; en français : mais qu’est-ce que c’est que ça ? Je croyais que checkout c’était pour récupérer l’état d’un fichier ?

      +

      En fait le mot checkout sert à la fois à revenir en arrière sur un fichier (MAIS PAS TOUTE UNE ARBORESCENCE où là ça sera reset --hard) et à changer de branche !

      +

      Je trouve ça carrément contre nature. Même si c’est totalement justifié du point de vue théorique voir le très bon article (en anglais) Git for Computer Scientist. Du point de vue interface utilisateur, on peut difficilement faire pire. On dirait que les mots clés sont utilisés pour piéger l’utilisateur.

      +
      +
        +
      • — Alors, essaye de deviner ce qu’il va falloir écrire pour faire cette opération ?
      • +
      • — Perdu. Essaye encore, cherche sur Internet (blaireau).
      • +
      • — Non, c’est toujours pas bon, recommence (sale nul).
      • +
      +
      +

      Bon alors, voilà, les défauts de Git. Mais, il a par contre beaucoup d’avantages. Une fois qu’on a compris le principe des branches légères. Tout devient plus clair. Même si en pratique, l’édition du fichier .git/config peut s’avérer un peu fastidieuse et surtout contre intuitive.

      +

      - Il faut aussi préciser qu’ayant travaillé sur les logiques multi-modales et en particulier sur les logiques temporelles (linéaires ou non), j’étais plus enclin à adhérer à cette vision des choses. “Ah mes premiers amours dans la recherche scientifique !”

      +
      +

      Conclusion

      +

      DCVS vs. CVS ?

      +

      Est-ce que ça valait la peine d’utiliser un système de “versionning” décentralisé ? Indéniablement la réponse est oui. On peut très bien vivre avec des systèmes de versions centralisés, mais la souplesse apportée par la facilité de “merger” différentes branches. De travailler de façon autonomes sur différentes parties d’un projets sont vraiment des plus appréciables. J’aurai vraiment du mal à revenir en arrière.

      +

      Est-ce que Git est meilleurs que Bazaar ?

      +

      En terme de fonctionnalités je dirai que Git est meilleurs. Par contre, je dois avouer qu’il s’agit d’un CVS qui s’est mis dans mes pattes. Or c’est exactement ce que je ne souhaitait pas lors de mon premier choix.

      +

      Je n’aurai pas dû avoir du mal à comprendre cette notion de branche légère qui doit être un patch sinon tu reçois des messages t’expliquant que tu es en retard. En réalité, Git différencie la notion d’arbre de la notion de branche. Ce qui n’est pas le cas dans Bazaar. Conceptuellement, c’est beaucoup plus simple de comprendre avec Bazaar.

      +

      Finalement ?

      +

      Pour conclure, j’utilise plus souvent Git que Bazaar et je dois dire que je préfère utiliser Git. Cependant, les commandes comme revert manquent cruellement avec Git. Pour l’instant je n’ai pas encore fait d’alias pour renommer les commandes Git comme je le souhaite.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-10-13 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2009-11-12-Git-for-n00b/index.html b/Scratch/fr/blog/2009-11-12-Git-for-n00b/index.html new file mode 100644 index 0000000..20d04ac --- /dev/null +++ b/Scratch/fr/blog/2009-11-12-Git-for-n00b/index.html @@ -0,0 +1,499 @@ + + + + + + YBlog - Git pour les nuls + + + + + + + + + + + + + +
      + + +
      +

      Git pour les nuls

      +
      +
      +
      +
      +
      + +

      Voici un tutoriel Git détaillé pour ceux qui en connaissent très peu sur les systèmes de versions. Vous comprendrez l’utilité de tels systèmes et surtout comment on se sert des systèmes de versions modernes, le tout en restant le plus pragmatique possible.

      +
      + +
      +

      Pour commencer, la conclusion

      +

      Voici la liste des commandes nécessaires et suffisantes pour utiliser Git. Il y en a très peu. Il est normal de ne pas les comprendre tout de suite mais c’est pour vous donner une idée. Malgré la longueur de l’article, 95% de l’utilisation de Git tiens dans les 7 commandes décrites ci-après.

      +

      Récupérer un projet :

      +
      git clone ssh://server/path/to/project
      +

      Utiliser Git tous les jours :

      +
      # get modifications from other
      +git pull
      +# read what was done
      +git log
      +
      +# Make local changes to files 
      +hack, hack, hack...
      +# list the modified files
      +git status
      +# show what I've done
      +git diff
      +
      +# tell git to version a new file
      +git add new/file
      +
      +# commit its own modifications 
      +# to its local branch
      +git commit -a -m "Fix bug #321"
      +
      +# send local modifications to other
      +git push
      +

      Cet article est écrit pour ceux qui en savent très peu sur les systèmes de version. Il est aussi écrit pour ceux qui n’ont pas suivi les progrès accomplis depuis CVS ou subversion (SVN). C’est pourquoi, dans un premier temps, j’explique rapidement quels sont les buts poursuivis par les systèmes de versions. J’explique ensuite comment installer et configurer Git. Puis, pour chaque action que doivent accomplir les DCVS je donne les commandes Git qui y correspondent.

      +

      Git pour quoi faire ?

      +
      + +

      Si tout ce qui vous intéresse c’est d’utiliser Git tout de suite. Lisez simplement les parties sur fond noir. Je vous conseille aussi de revenir relire tout ça un peu plus tard, pour mieux comprendre les fondements des systèmes de versions et ne pas faire de bêtises quand vous les utilisez.

      +
      + +

      Git est un DCVS, c’est-à-dire un système de versions concurrentes décentralisé. Analysons chaque partie de cette appellation compliquée.

      +

      Système de versions

      +

      Tout d’abord, les systèmes de versions gèrent des fichiers. Quand on travaille avec des fichiers sans système de version voilà ce qui arrive souvent :

      +

      Lorsqu’on modifie un fichier un peu critique et qu’on a pas envie de perdre, on se retrouve souvent à le recopier sous un autre nom. Par exemple

      +
      $ cp fichier_important.c fichier_important.c.bak
      +

      Du coups, ce nouveau fichier joue le rôle de backup. Si on casse tout, on peut toujours écraser les modifications que nous avons faites. Évidemment le problème avec cette façon de faire c’est que ce n’est pas très professionnel. Et puis c’est un peu limité. Si on veut faire trois ou quatre modifications on se retrouve avec plein de fichiers. Parfois avec des nom bizarres comme :

      +
      fichier_important.c.bak
      +fichier_important.c.old
      +fichier_important.c.Bakcup
      +fichier_important.c.BAK.2009-11-14
      +fichier_important.c.2009.11.14
      +fichier_important.c.12112009
      +old.fichier_important.c
      +

      Bon alors si on veut que ça marche il faut se fixer des conventions de nommage. Les fichiers prennent beaucoup de place alors que souvent il n’y a que quelques lignes différentes entre le fichier et son backup…

      +

      Heureusement les systèmes de version viennent à la rescousse.

      +

      Il suffit de signaler que l’on va faire une nouvelle version d’un fichier et le système de version se débrouille pour l’enregistrer quelque part où on pourra facilement le retrouver. Et en général, le système de version fait les choses bien. C’est-à-dire qu’il n’utilise que très peu d’espace disque pour faire ces backups.

      +

      Il fut un temps où les versions étaient gérées fichier par fichier. Je pense à CVS. Puis on s’est vite aperçu qu’un projet c’est un ensemble de fichiers cohérents. Et donc il ne suffit pas de pouvoir revenir en arrière par fichier, mais plutôt dans le temps. Les numéros de versions sont donc passé d’un numéro par fichier à un numéro par projet tout entier.

      +

      Ainsi on peut dire, «je veux revenir trois jours en arrière», et tous les fichiers se remettent à jour.

      +
      + +

      Qu’apportent les systèmes de versions ? (je n’ai pas tout mentionné)

      +
        +
      • backup automatique de tous les fichiers: Revenir dans le temps. ;
      • +
      • donne la possibilité de voir les différences entre chaque version et les différences entre la version en cours et les modifications locales ;
      • +
      • permet de poser un tag sur certaines versions et ainsi pouvoir s’y référer facilement ;
      • +
      • permet d’avoir un historique des modifications. Car en général il est demandé aux utilisateurs d’ajouter un petit commentaire à chaque nouvelle version.
      • +
      +
      + +

      concurrentes

      +

      Les systèmes de versions sont déjà intéressants pour gérer ses projets personnels. Car ils permettent de mieux organiser celui-ci. De ne (presque) plus se poser de questions à propos des backups. Je dis presque parce qu’il faut quand même penser à protéger par backup son repository. Mais là où les systèmes de versions deviennent vraiment intéressants, c’est pour la gestion de projets à plusieurs.

      +

      Commençons par un exemple avec un projet fait par deux personnes ; Alex et Béatrice. Sur un fichier contenant une liste de dieux Lovecraftiens :

      +
      Cthulhu
      +Shubniggurath
      +Yogsototh
      +

      Disons que Alex est chez lui, il modifie le fichier :

      +
      +
      +Cthulhu
      +Shubniggurath
      +Soggoth
      +Yogsototh
      +
      +
      + +

      puis il envoi ce fichier sur le serveur du projet. Ainsi sur le serveur, il y a le fichier d’Alex.

      +

      Ensuite c’est Béatrice qui n’a pas récupéré le fichier d’Alex sur le serveur qui fait une modification.

      +
      +
      +Cthulhu
      +Dagon
      +Shubniggurath
      +Yogsototh
      +
      +
      + +

      Puis Béatrice envoi son fichier sur le serveur.

      +

      La modification d’Alex est perdue. Encore une fois les systèmes de versions sont là pour résoudre ce type de soucis.

      +

      Un système de version aurait mergé les deux fichiers au moment où Béatrice voulait envoyer la modification sur le serveur. Et comme par magie, sur le serveur le fichier deviendra :

      +
      +
      +Cthulhu
      +Dagon
      +Shubniggurath
      +Soggoth
      +Yogsototh
      +
      +
      + +

      En pratique, au moment où Béatrice veut envoyer ses modifications, le système de version la préviens qu’une modification a eu lieu sur le serveur. Elle utilise la commande qui rapatrie les modifications localement et qui va mettre à jour le fichier. Ensuite Béatrice renvoie le nouveau fichier sur le serveur.

      +
      + +

      Qu’apportent les Systèmes de Versions Concurrentes ?

      +
        +
      • récupérer sans problème les modifications des autres ;
      • +
      • envoyer sans problème ses modifications aux autres ;
      • +
      • permet de gérer les conflits. Je n’en ai pas parlé, mais quand un conflit arrive (ça peut arriver si deux personnes modifient la même ligne avec deux contenus différents), les SVC proposent leur aide pour les résoudre. J’en dirai un mot plus loin.
      • +
      • permet de savoir qui a fait quoi et quand
      • +
      +
      + +

      décentralisé

      +

      Ce mot n’est devenu populaire que très récemment dans le milieu des systèmes de version. Et bien ça veut dire principalement deux choses.

      +

      Tout d’abord, jusqu’à très récemment (SVN) il fallait être connecté sur un serveur distant pour avoir des informations sur un projet. Comme avoir l’historique. Les nouveaux systèmes décentralisés permettent de travailler avec un REPOSITORY (le répertoire contenant tous les backups, et les différentes info nécessaires au fonctionnement du système de versions) local au projet. Ainsi on peut avoir l’historique du projet sans avoir à se connecter au serveur.

      +

      Toutes les instances de projets peuvent vivre de façon indépendantes.

      +

      Pour préciser, les systèmes de versions concurrentes décentralisés sont basés sur la notion de branche.

      +

      Et la signification pratique est très importante. Ça veut dire que tout les utilisateurs travaillent de façon complètement indépendante les uns des autres. Et c’est l’outil de version qui se charge de mettre tout ça ensemble.

      +

      Ça va même encore plus loin. Ça permet de développer plusieurs features de manière complètement indépendantes. Sous les autres systèmes c’était plus difficile.

      +

      L’exemple type :

      +
      +

      Je développe mon projet. Je suis en train de l’améliorer. Lorsqu’un bug urgent est reporté.

      +

      Je peux très facilement avec un système décentralisé, revenir sur la version qui pose problème. Résoudre le bug. Renvoyer les modifications. Puis revenir à ma version avec les améliorations en cours. Et même récupérer la correction de bug dans ma nouvelle version avec les améliorations.

      +

      Dans un système non décentralisé, cela est possible, mais fastidieux. Les systèmes décentralisés rendent ce type de comportement très naturels. Ainsi, il devient naturel de tirer des branches pour toutes les features, les bug…

      +
      +
      + +

      Avantages donnés par la décentralisation des systèmes de versions concurrentes :

      +
        +
      • Possibilité de travailler sans être connecté au serveur de version ;
      • +
      • Possibilité de créer beaucoup de patches atomiques ;
      • +
      • Grande facilité de maintenance de plusieurs versions différentes de la même application.
      • +
      +
      + +

      Pour résumer

      +

      Résumons l’ensemble des choses que l’on peut faire facilement avec un DCVS :

      +

      Systèmes de versions

      +
        +
      • revenir dans le temps ;
      • +
      • lister les différences entre chaque version ;
      • +
      • nommer certaines versions pour s’y référer facilement ;
      • +
      • afficher l’historique des modifications.
      • +
      +

      Concurrentes

      +
        +
      • récupérer les modifications des autres ;
      • +
      • envoyer ses modifications aux autres ;
      • +
      • permet de savoir qui a fait quoi et quand ;
      • +
      • gestion des conflits.
      • +
      +

      Décentralisé

      +
        +
      • manipuler facilement des branches
      • +
      +

      Maintenant voyons comment obtenir toutes ces choses facilement avec Git.

      +

      Avant l’utilisation, la configuration

      +

      installation

      +

      Sous Linux Ubuntu ou Debian :

      +
      $ sudo apt-get install git
      +

      Sous Mac OS X :

      + +
      $ sudo port selfupdate
      +
      +$ sudo port install git-core
      +

      Configuration globale

      +

      Enregistrez le fichier suivant comme le fichier ~/.gitconfig.

      +
      [color]
      +    branch = auto
      +    diff   = auto
      +    status = auto
      +[alias]
      +    st        = status
      +    co        = checkout
      +    br        = branch
      +    lg        = log --pretty=oneline --graph
      +    logfull   = log --pretty=fuller --graph --stat -p
      +    unstage   = reset HEAD
      +    # there should be an article on what this command do
      +    uncommit = !zsh -c '"if (($0)); then nb=$(( $0 - 1 )); else nb=0; fi; i=0; while ((i<=nb)); do git revert -n --no-edit HEAD~$i; ((i++)); done; git commit -m \"revert to $0 version(s) back\""'
      +    undomerge = reset --hard ORIG_HEAD
      +	conflict  = !gitk --left-right HEAD...MERGE_HEAD
      +    # under Mac OS X, you should use gitx instead
      +	# conflict    = !gitx --left-right HEAD...MERGE_HEAD
      +[branch]
      +	autosetupmerge = true
      +

      Vous pouvez obtenir le même résultat en utilisant pour chaque entrée la commande git config --global. Configurez ensuite votre nom et votre email. Par exemple si vous vous appelez John Doe et que votre email est john.doe@email.com. Lancez les commandes suivantes :

      +
      $ git config --global user.name John Doe
      +
      +$ git config --global user.email john.doe@email.com
      +

      Voilà, la configuration de base est terminée. J’ai créé dans le fichier de configuration global des alias qui vont permettre de taper des commandes un peu plus courtes.

      +

      Récupération d’un projet déjà versionné

      +

      Si un projet est déjà versionné avec Git vous devez avoir une URL pointant vers les sources du projet. La commande a exécuter est alors très simple.

      +
      $ cd ~/Projets
      +$ git clone git://main.server/path/to/file
      +

      S’il n’y a pas de serveur git sur le serveur distant, mais que vous avez un accès ssh, il suffit de remplacer le git de l’url par ssh. Pour ne pas avoir à entrer votre mot de passe à chaque fois le plus simple est de procéder comme suit :

      +
      $ ssh-keygen -t rsa
      +

      Répondez aux question et n’entrez surtout PAS de mot de passe. Ensuite copiez les clés sur le serveur distant. Ce n’est pas la façon la plus sûre de procéder. L’idéal étant d’écrire quand même un mot de passe et d’utiliser ssh-agent.

      +

      Ensuite le plus simple, si vous possédez ssh-copy-id (sous Ubuntu par exemple) :

      +
      me@locahost$ ssh-copy-id -i ~/.ssh/id_rsa.pub me@main.server
      +

      ou manuellement :

      +
      me@locahost$ scp ~/.ssh/id_rsa.pub me@main.server:
      +me@locahost$ ssh me@main.server
      +password:
      +me@main.server$ cat id_rsa.pub >> ~/.ssh/authorized_keys
      +me@main.server$ rm id_rsa.pub
      +me@main.server$ logout
      +

      Maintenant vous n’avez plus besoin de taper votre mot de passe pour accéder à main.server. Et donc aussi pour les commandes git.

      +

      Créer un nouveau projet

      +

      Supposons que vous avez déjà un projet avec des fichiers. Alors il est très facile de le versionner.

      +
      $ cd /path/to/project
      +$ git init
      +$ git add .
      +$ git commit -m "Initial commit"
      +

      Une petite précision. Si vous ne souhaitez pas versionner tous les fichiers. Par exemple, les fichiers de compilations intermédiaires. Alors il faut les exclure. Pour cela, avant de lancer la commande git add .. Il faut créer un fichier .gitignore qui va contenir les pattern que git doit ignorer. Par exemple :

      +
      *.o
      +*.bak
      +*.swp
      +*~
      +

      Maintenant si vous voulez créer un repository sur un serveur distant, il faut absolument qu’il soit en mode bare. C’est-à-dire que le repository ne contiendra que la partie contenant les informations utile à la gestion de git, mais pas les fichiers du projet. Sans rentrer dans les détails, il suffit de lancer :

      +
      $ cd /path/to/local/project
      +$ git clone --bare . ssh://server/path/to/project
      +

      Les autres pourront alors récupérer les modifications via la commande vue précédemment :

      +
      git clone ssh://server/path/to/project
      +

      Résumé de la seconde étape

      +

      Vous avez maintenant un répertoire sur votre ordinateur local. Il est versionné. Vous pouvez vous en rendre compte parcequ’à la racine (et à la racine seulement), il y a un répertoire .git. Ce répertoire contient tous les fichiers nécessaires au bon fonctionnement de Git.

      +

      Il ne reste plus qu’à savoir comment s’en servir maintenant pour obtenir toutes les jolies promesses faites dans la première partie.

      +

      Et c’est parti !

      +

      Voici une parmi de nombreuses autres façon d’utiliser Git. Cette méthode est nécessaire et suffisante pour travailler seul ou en collaboration sur un projet commun. Cependant, on peut faire beaucoup mieux avec Git que ce workflow (en langage anglo-saxon).

      +

      Utilisation basique

      +

      La façon immédiate de travailler avec Git :

      +
        +
      • récupérer les modifications des autres git pull
      • +
      • voir les détails de ces modifications git log
      • +
      • Plusieurs fois:
      • +
      • Faire une modification atomique
      • +
      • verifier le details de ses modifications git status et git diff
      • +
      • indiquer si nécessaire que de nouveaux fichiers doivent être versionnés git add [file]
      • +
      • enregistrer ses modifications
        git commit -a -m "message"
      • +
      • envoyer ses modifications aux autres git push (refaire un git pull si le push renvoie une erreur).
      • +
      +

      Voilà, avec ces quelques commandes vous pouvez utiliser Git sur un projet avec d’autres personnes. Même si c’est suffisant, il faut quand même connaître une chose avant de se lancer ; la gestion des conflits.

      +

      Gestion des conflits

      +

      Les conflits peuvent survenir lorsque vous modifiez les même lignes de codes sur le même fichier d’une autre branche que vous mergez. Ça peut sembler un peu intimidant, mais avec Git ce genre de chose est très facile a régler.

      +

      exemple

      +

      Vous partez du fichier suivant :

      +
      +
      Zoot 
      +
      + +

      et vous modifiez une ligne

      +
      +
      +Zoot the pure
      +
      +
      + +

      sauf que pendant ce temps, un autre utilisateur a aussi modifié cette ligne et a fait un push de sa modification.

      +
      +
      +Zoot, just Zoot
      +
      +
      + +

      Maintenant quand vous lancez la commande

      +
      +
      $ git pull
      +remote: Counting objects: 5, done.
      +remote: Total 3 (delta 0), reused 0 (delta 0)
      +Unpacking objects: 100% (3/3), done.
      +From /home/yogsototh/tmp/conflictTest
      +   d3ea395..2dc7ffb  master     -> origin/master
      +Auto-merging foo
      +CONFLICT (content): Merge conflict in foo
      +Automatic merge failed; fix conflicts and then commit the result.
      +
      + +

      Notre fichier foo contient alors :

      +
      +
      +<<<<<<< HEAD:foo
      +Zoot the pure
      +=======
      +Zoot, just Zoot
      +>>>>>>> 2dc7ffb0f186a407a1814d1a62684342cd54e7d6:foo
      +
      +
      + +

      Résolution du conflit

      +

      Régler le conflit, il suffit d’éditer le fichier, par exemple en écrivant :

      +
      +
      +Zoot the not so pure
      +
      +
      + +

      et de ‘commiter’ tout simplement :

      +
      +
      git commit -a -m "conflict resolved"
      +
      + +

      Maintenant vous êtes fin prêt pour utiliser Git. Sauf que Git, c’est un outil qui permet de faire beaucoup plus que juste ça. Nous allons maintenant voir comment utiliser les fonctionnalités de Git qui n’étaient pas disponibles avec CVS et consorts.

      +

      Pourquoi Git est cool ?

      +

      Parce que grace à Git vous pouvez travailler sur plusieurs partie du projet de façon complètement isolée les unes des autres. Ça c’est la partie décentralisée de Git.

      +

      Toutes les branches locales utilisent le même répertoire. Ainsi on peu changer de branche très aisément et rapidement. On peut aussi changer de branche alors que certains fichier sont en cours de modifications. On peut même pousser le vice jusqu’à modifier un fichier, changer de branche, commiter une partie seulement des modifications de ce fichier dans la branche courante. Revenir dans l’ancienne branche et commiter à nouveau les modifications restantes. Et merger dans une troisième branche les deux modifications.

      +

      Avec la command git rebase on peut après coup, décider que certaines modifications devaient aller dans certaines branches, que d’autres ne servaient à rien. C’est une commande vraiment très puissante pour organiser l’historique.

      +

      En pratique, qu’est-ce que ça signifie ? Mieux qu’avec tous les autres systèmes de versions, vous pouvez utiliser Git pour vous concentrer sur votre code. En effet, on peut envoyer les commits après avoir coder. Par exemple, vous pouvez coder sur la résolution du bug b01, du bug b02 et de la feature f03. Puis ensuite, vous pouvez créer une branche par bug et par feature. Puis commiter les modifications pour chaque branche et chaque feature. Puis finalement merger tous les modifications dans la branche principale.

      +

      Tout a été pensé pour vous permettre de coder d’abord, puis de vous occuper du système de version plus tard. Bien entendu, faire des commit atomique au fur et à mesure du code permet de gagner du temps et de ne pas trop s’embêter pour organiser les branches. Mais rien ne vous y oblige. Par contre faire la même chose dans d’autres systèmes de versions n’est absolument pas naturel.

      +

      Avec Git vous pouvez aussi dépendre de plusieurs sources. Ainsi, plutôt que d’avoir un serveur centralisé, vous pouvez avoir plusieurs sources. Vous pouvez définir ce genre de chose très finement.

      +

      Ce qui change le plus avec Git c’est la vision d’un projet centralisé sur un serveur avec plusieurs personnes qui travaillent dessus. Avec Git plusieurs personnes peuvent travailler sur le même projet, mais sans nécessairement avoir un repository de référence. On peut très facilement résoudre un bug et envoyer le patch à plein d’autres versions du projet.

      +

      Liste de commandes

      +

      Les commandes pour chaque choses

      +

      Dans la première partie, nous avons vu la liste des problèmes résolus par Git. En résumé Git doit pouvoir :

      +
        +
      • récupérer les modifications des autres ;
      • +
      • envoyer ses modifications aux autres ;
      • +
      • revenir dans le temps ;
      • +
      • lister les différences entre chaque version ;
      • +
      • nommer certaines versions pour s’y référer facilement ;
      • +
      • afficher l’historique des modifications ;
      • +
      • savoir qui a fait quoi et quand ;
      • +
      • gérer des conflits ;
      • +
      • manipuler facilement des branches.
      • +
      +

      récupérer les modifications des autres

      +
      $ git pull
      +

      envoyer ses modifications aux autres

      +
      $ git push
      +

      ou plus généralement

      +
      $ git pull
      +$ git push
      +

      revenir dans le temps

      +

      Pour toute l’arborescence

      +
      $ git checkout
      +
      $ git revert
      +

      revenir trois versions en arrière

      +
      $ git uncommit 3
      +

      Revenir avant le dernier merge (s’il s’est mal passé).

      +
      $ git revertbeforemerge
      +

      Pour un seul fichier

      +
      $ git checkout file
      +$ git checkout VersionHash file
      +$ git checkout HEAD~3 file
      +

      lister les différences entre chaque version

      +

      liste les fichiers en cours de modifications

      +
      $ git status
      +

      différences entre les fichiers de la dernière version et les fichiers locaux.

      +
      $ git diff
      +

      liste les différences entre les fichier d’une certaine version et les fichiers locaux.

      +
      $ git diff VersionHash fichier
      +

      nommer certaines versions pour s’y référer facilement

      +
      $ git tag 'toto'
      +

      afficher l’historique des modifications

      +
      $ git log
      +$ git lg
      +$ git logfull
      +

      savoir qui a fait quoi et quand

      +
      $ git blame fichier
      +

      gérer des conflits

      +
      $ git conflict
      +

      manipuler facilement des branches

      +

      Pour créer une branche :

      +
      $ git branch branch_name
      +

      Pour changer de branche courante :

      +
      $ git checkout branch_name
      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-11-12 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2009-12-06-iphone-call-filter/index.html b/Scratch/fr/blog/2009-12-06-iphone-call-filter/index.html new file mode 100644 index 0000000..cacb6e3 --- /dev/null +++ b/Scratch/fr/blog/2009-12-06-iphone-call-filter/index.html @@ -0,0 +1,125 @@ + + + + + + YBlog - Filtrage d'appel avec l'iPhone + + + + + + + + + + + + + +
      + + +
      +

      Filtrage d'appel avec l'iPhone

      +
      +
      +
      +
      +

      Il est vraiment incroyable que le filtrage d’appel soit impossible avec un iPhone ! Le seul intérêt que j’y vois, c’est une négociation avec les opérateurs pour interdire aux utilisateurs de passer à travers la publicité. C’est tout simplement inacceptable.

      +

      Je suis un utilisateur λ de l’iPhone. Le seul moyen de filtrer ses appels, de faire des blacklists ou autre c’est de jailbreaker son iPhone. Et je n’en ai aucune envie. Alors si comme moi, vous trouvez ça inacceptable, envoyez un mot à Apple : http://www.apple.com/feedback/iphone.html

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-12-06 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2009-12-14-Git-vs--Bzr/index.html b/Scratch/fr/blog/2009-12-14-Git-vs--Bzr/index.html new file mode 100644 index 0000000..28c0e75 --- /dev/null +++ b/Scratch/fr/blog/2009-12-14-Git-vs--Bzr/index.html @@ -0,0 +1,208 @@ + + + + + + YBlog - Git ou Bazaar ? + + + + + + + + + + + + + +
      + + +
      +

      Git ou Bazaar ?

      +
      +
      +
      +
      +
      + +

      Même si je considère que git a beaucoup de points noirs, je pense qu’il reste le meilleur DCVS à ce jour avec lequel travailler. C’est pourquoi je commencerai par parler des qualité de bazaar qui manquent à git. Ensuite seulement je donnerai le seul avantage de git qui suffit à le rendre préférable à Bazaar.

      +
      + +

      La découverte des DCVS

      +

      À savoir avant de débuter l’article. Je suis, comme beaucoup, un ancien utilisateur de subversion. Je trouve subversion très bien, mais j’ai été conquis par les capacités supplémentaires apportées par les systèmes de versions concurrentes décentralisés.

      +

      Il y a deux façon de percevoir les systèmes de versions. Soit on voit un système de branches (voir le très bon article sur betterexplained). Soit on peut voir les systèmes de versions comme des moyens d’appliquer des patches. C’est-à-dire que l’on peut soit se concentrer sur les nœuds soit sur les transitions du graphe induit par les différentes versions d’un projet.

      +

      Pour git, c’est plutôt ce deuxième point de vue qui a été adopté. C’est un peu normal, étant donné que c’est Linus Torvald qui l’a inventé pour combler les problèmes inhérent aux problèmes de création de code dans le noyau Linux. Et comme historiquement, la communauté Linux se base beaucoup sur les patches, il semblait logique que ce soit ce second point de vue qui soit adopté.

      +

      J’ai d’abord été convaincu par Bazaar. Pourquoi ? Les arguments en faveur de bazaar étaient : facilité d’utilisation en particulier, facilité d’adaptation pour tous ceux qui étaient habitués à subversion. Comme c’était mon cas, et que lorsque j’avais essayé de suivre la doc Git à l’époque c’était un peu épique. Puis avec le temps, je me suis dit que je n’allais quand même pas mourir idiot et que j’allais me mettre sérieusement à git histoire de voir si ses défenseurs avaient vraiment raison.

      +

      Mon dieu, que ce fut fastidieux. La terminologie est affreuse ! Et ce n’est rien de le dire.

      +

      Là où Bazaar est meilleur que git

      +

      Par exemple, checkout qui sert certainement à la même chose du point de vue technique, est dans l’usage un terme employé pour faire des actions qui semblent très différentes à un utilisateur λ. Exemple :

      +
      + git checkout pipo +
      + +

      annule une modification courante du fichier pipo

      +
      + git checkout pipo +
      + +

      change de la branche courante vers la branche pipo

      +

      Et là, comme moi, vous remarquez que la même commande à deux sens complètement différents. Comment ça se passe alors, quand il y a une branche pipo et un fichier pipo alors ? Et bien par défaut, ça change de branche. Pour lever l’ambigüité il faut utiliser la syntaxe

      +
      + git checkout ./pipo +
      + +

      Oui, bon… Voilà, voilà, voilà….

      +

      Ça marche, mais ce n’est pas très convivial. D’autant plus que le mot clé checkout signifiait sous CVS et SVN l’opération pour récupérer un projet distant.

      +

      Là où la différence se creuse c’est avec la terminologie Bazaar qui est bien plus naturelle. Car il n’y a pas de commande pour changer de branche, puisqu’il y a une branche par répertoire. Ainsi, pour changer de branche, il suffit de faire cd path/to/branch. Et pour revenir en arrière :

      +
      + bzr revert pipo +
      + +

      De plus, la plupart des commandes bazaar prennent en paramètre un numéro de révision, par exemple pour revenir 3 versions précédentes il suffit d’écrire :

      +
      + bzr revert -r -3 pipo +
      + +

      L’équivalent sous git est beaucoup plus cryptique :

      +
      + bzr checkout HEAD~3 pipo +
      + +

      Encore un fois, Bazaar est bien plus lisible.

      +

      Revenir dans le temps pour tout le projet :

      +

      avec Bazaar :

      +
      + bzr revert -r -3 pipo +
      + +

      et avec git ? git checkout ? Bien sûr que non voyons ! Ce serait bien trop simple. Ce que l’on trouve dans les forums c’est :

      +
      + git reset –hard HEAD~3 +
      + +

      Sauf que cette syntaxe est horrible. Elle oublie ‘réellement’ les révisions. Il faut donc l’utiliser avec prudence. Mais en effet, je conseillerai plutôt :

      +
      + git checkout HEAD~3 – . && git commit -m ‘back in time’ +
      + +

      Histoire d’avoir la branche backup sous la main, car sinon, on risque de perdre définitivement la version courante de HEAD. Qui ramène la branche locale à ce point. Mais il reste des erreur s’il y a eu des ajouts de fichier entre temps. Le seul et l’unique vraiment propre de revenir en arrière dans git c’est de lancer la commande suivante :

      +
      + for i in (seq02); dogitrevert − n −  − no − editheadi; done git commit -m “reverted 3 versions back” +
      + +

      ce qui signifie sur un système UNIX en zsh (ou bash) faire git revert de toutes les dernières versions. Même si quelqu’un d’autre à fait un pull de vos modification intermédiaire il ne sera pas embêté et il sera au courant de ce qu’il s’est passé.

      +

      La règle est simple : Ne JAMAIS utiliser la commande git reset avec une version que d’autres personnes auraient pu avoir fetcher.

      +

      Voilà, c’est dit. Découvrir ça m’a pris pas mal de temps, avec plein d’essai de tous les cotés. Le plus sûr reste toujours la méthode vue plus haut. Si vous souhaitez automatiser cela, le plus simple est d’ajouter l’alias suivant à votre fichier ~/.gitconfig. Bien sûr l’alias ne fonctionnera que sur les environnement possédant zsh, ce qui est le cas de la plupart des environnements UNIX (Ubuntu, Mac OS X…).

      +
      + [alias] uncommit = !zsh -c ‘“if ((0)); thennb = (( 0 − 1)); elsenb = 0; fi; i = 0; while((i <  = nb)); dogitrevert − n −  − no − editHEADi; ((i++)); done; git commit -m "revert to $0 version(s) back"”’ +
      + +

      Ce qui fait que git est le meilleur DCVS jusqu’à aujourd’hui

      +

      Après avoir énoncé les cotés négatifs (et je les trouve nombreux) de git. Voici les cotés positifs qui a eux seul valent la peine de se coltiner tous les problèmes inhérent à git.

      +

      Cheap branching

      +

      Vous travaillez toujours dans le même répertoire principal. Par exemple, vous pouvez travailler sur deux corrections de bug. Disons fix1 et fix2 nécessitant la modification respective de file1 et file2. Vous pouvez travailler dans n’importe quel ordre sur vos deux fichiers dans la branche master. Puis, une fois votre travail fini. Aller dans la branche fix1 pour commiter file1. Puis aller dans la branche fix2 pour commiter file2. Et enfin, merger les deux branches dans master.

      +
      + > vim file1 > vim file2 > git br fix1 > git add file1 > git commit -m ‘fix1’ > git br fix2 > git add file2 > git commit -m ‘fix2’ > git commit master > git merge fix1 > git merge fix2 +
      + +

      Et il est vraiment très agréable de ne pas se soucier d’être dans la bonne branche. Vous n’avez à vous occuper que de votre code et seulement ensuite vous occuper du système de version.

      +

      Sous Bazaar, il m’est souvent arriver de coder dans la mauvaise branche. Pour récupérer le coup, on doit copier les modifications du fichier dans la bonne branche et faire un revert sur le fichier en question, puis aller dans la bonne branche pour commiter les modifications. Enfin, la plupart du temps, je me trompe de branche et puis tant pis, je merge les deux tout en sachant que c’est sale.

      +

      C’est pourquoi je préfère utiliser git. Si Bazaar venait à implémenter ce système de cheap branching, je le replacerai certainement en tête.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2009-12-14 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2010-01-04-Change-default-shell-on-Mac-OS-X/index.html b/Scratch/fr/blog/2010-01-04-Change-default-shell-on-Mac-OS-X/index.html new file mode 100644 index 0000000..6769f8c --- /dev/null +++ b/Scratch/fr/blog/2010-01-04-Change-default-shell-on-Mac-OS-X/index.html @@ -0,0 +1,129 @@ + + + + + + YBlog - Changer le shell par défaut sous Mac OS X + + + + + + + + + + + + + +
      + + +
      +

      Changer le shell par défaut sous Mac OS X

      +
      +
      +
      +
      +

      Je viens de trouver le moyen de changer son shell par défaut sous Mac OS X. Cette note est plus pour moi. Mais elle peut aussi servir à quelqu’un d’autre. Il suffit de lancer la commande :

      +
      + > chsh +
      + + +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2010-01-04 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/index.html b/Scratch/fr/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/index.html new file mode 100644 index 0000000..23331ab --- /dev/null +++ b/Scratch/fr/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/index.html @@ -0,0 +1,183 @@ + + + + + + YBlog - Fontes adoucies sous Ubuntu Firefox + + + + + + + + + + + + + +
      + + +
      +

      Fontes adoucies sous Ubuntu Firefox

      +
      +
      +
      +
      +

      Voici comment faire pour ne plus utiliser les fontes Microsoft© sous Linux Ubuntu pour avoir de belles fontes adoucies (anti aliased) qui ne font pas mal aux yeux sous Firefox.

      +

      modifiez le fichier /etc/fonts/local.conf en y incluant le contenu suivant :

      +
      +
      
      +<?xml version="1.0"?>
      +<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
      +<fontconfig>
      +
      +<!-- Miscellaneous settings -->
      +
      +<include ignore_missing="yes">misc.conf</include>
      +
      +<!-- Define alias -->
      +
      +<include ignore_missing="yes">alias.conf</include>
      +
      +<!-- Rules for Microsoft fonts -->
      +
      +<include ignore_missing="yes">msfonts-rules.conf</include>
      +
      +  <match target="pattern" name="family" >
      +      <test name="family" qual="any" >
      +          <string>Tahoma</string>
      +      </test>
      +      <edit mode="assign" name="family" >
      +          <string>Verdana</string>
      +      </edit>
      +  </match>
      +  <selectfont>
      +      <acceptfont>
      +          <pattern>
      +              <patelt name="family"> 
      +                <string>Lucida Grande</string> 
      +              </patelt>
      +          </pattern>
      +      </acceptfont>
      +  </selectfont>
      +
      +  <match target="pattern" name="family" >
      +      <test name="family" qual="any" >
      +          <string>Georgia</string>
      +      </test>
      +      <edit mode="assign" name="family" >
      +          <string>Georgia</string>
      +      </edit>
      +  </match>
      +  <selectfont>
      +      <acceptfont>
      +          <pattern>
      +              <patelt name="family"> 
      +                <string>Century Schoolbook L</string> 
      +              </patelt>
      +          </pattern>
      +      </acceptfont>
      +  </selectfont>
      +
      +</fontconfig>
      +
      + +

      J’espère que ça a pu aider quelqu’un qui comme moi pleurait en regardant des fontes aussi laides.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2010-01-12 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2010-02-15-All-but-something-regexp/index.html b/Scratch/fr/blog/2010-02-15-All-but-something-regexp/index.html new file mode 100644 index 0000000..6a89920 --- /dev/null +++ b/Scratch/fr/blog/2010-02-15-All-but-something-regexp/index.html @@ -0,0 +1,157 @@ + + + + + + YBlog - Expression régulière pour tout sauf quelquechose + + + + + + + + + + + + + +
      + + +
      +

      Expression régulière pour tout sauf quelquechose

      +
      +
      +
      +
      +

      Parfois vous ne pouvez simplement pas écrire :

      +
      + if str.match(regexp) and not str.match(other_regexp) do_something +
      + +

      et vous devez obtenir le même comportement avec seulement une expression régulière. Le problème c’est que le complémentaire des régulier n’est pas régulier. Celà peut s’avérer impossible.

      +

      Cependant, pour certaines expressions ce peut être possible. Disons que vous souhaitez matcher toutes les lignes contenant le mot bull, mais que vous ne souhaitez pas matcher bullshit. Voici une façon sympa d’y arriver :

      +
      +

      # matcher toute les chaines qui # matchent ‘bull’ (bullshit compris) /bull/

      +

      matcher toutes les chaines qui

      +

      contiennent ‘bull’ sauf ‘bullshit’

      +

      /bull([^s]|)∣bulls([h]∣)| bullsh([^i]|)∣bullshi([t]∣)/

      +

      une autre façon de l’écrire serait

      +/bull([^s]|s([h]∣)|sh([^i]|)∣shi([t]∣))/
      +
      + +

      Regardons de plus près. Dans la première ligne, l’expression est : bull([^s]|$), pourquoi avons nous besoin du $ ? Parce que sans lui, le mot bull ne serait pas matché. Cette expression signifie :

      +
      +

      La chaine finie par bull
      ou,
      contient bull suivi d’une lettre différente de s.

      +
      +

      Et voilà. J’espère que ça a pu vous aider.

      +Notez que cette méthode n’est pas toujours la meilleure. Par exemple essayons d’écrire une expression régulière équivalente à l’expression conditionnelle suivante : +
      + # Commence avec ‘a’: ^a # Se finit par ‘a’: c$ # Contient ‘b’: .b. # Mais n’est pas ‘axbxc’ if str.match(/^a.b.c / )andnotstr. match( / axbxc/) do_something end +
      + +

      Une solution est :

      +
      + /abc| # longueur 3 a.bc| # longueur 4 ab.c| a[^x]b[^x]c| # longueur 5 a…b.c| # longueur >5 a.b…c/ +
      + +

      Cette solution utilise la longueur maximale de la chaine qui ne doit pas être matchée. Il existe certainement d’autres méthodes. Mais la leçon importante c’est qu’il n’est pas naturel d’exclure des solutions d’un expression régulière.

      +
      +

      Il peut être démontré que tout ensemble régulier privé d’un ensemble fini est aussi régulier.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2010-02-15 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2010-02-16-All-but-something-regexp--2-/index.html b/Scratch/fr/blog/2010-02-16-All-but-something-regexp--2-/index.html new file mode 100644 index 0000000..e02b2d7 --- /dev/null +++ b/Scratch/fr/blog/2010-02-16-All-but-something-regexp--2-/index.html @@ -0,0 +1,211 @@ + + + + + + YBlog - Tout sauf quelquechose en expression régulière. + + + + + + + + + + + + + +
      + + +
      +

      Tout sauf quelquechose en expression régulière.

      +
      +
      +
      +
      +

      Dans mon précédent article j’ai donné certaines astuces pour matcher ‘tout sauf quelque chose’. De la même manière, un truc pour matcher la chaine de caractère la plus petite possible. Disons que vous voulez matcher la chaine de caractère entre ‘a’ et ‘b’. Par exemple, vous voulez matcher :

      +
      +a.....a......b..b..a....a....b...
      +
      + +

      Voici les deux erreurs communes et une solution :

      +
      +/a.*b/
      +a.....a......b..b..a....a....b...
      +
      + +

      La première erreur vient de l’utilisation du terrible .*. Parce que vous allez matcher la chaîne de caractère la plus longue possible.

      +
      +/a.*?b/
      +a.....a......b..b..a....a....b...
      +
      + +

      L’autre manière naturelle de répondre à ce problème est de changer la greediness. Mais ce n’est pas assez parce que vous allez matcher du premier a au premier b après celui-ci. On peut alors constater que votre chaine de caractère ne devrait comprendre ni la lettre a ni la lettre b. Ce qui emène à la dernière solution élégante.

      +
      +/a[^ab]*b/
      +a.....a......b..b..a....a....b...
      +
      + +

      Jusqu’ici, c’était facile. Maintenant comment fait vous quand au lieu de a vous avez une chaine de caractère ?

      +Par exemple, vous voulez matcher: +
      + +
    • +… +
    • + +
    • + +C’est un peu difficile. Vous devez matcher +
      + +
    • +[anything not containing ] +
    • +
      +
      + +

      La première méthode serait d’utiliser le même rainsonnement que dans mon article précédent. Ici un premier essai :

      +
      + +
    • +([^<]|<[^l]|])* +
    • +
      +
      + +Mais il y a encore une erreur. Pensez à la chaine de caractère suivante : +
      + +
    • +… +
    • + +Cette chaine ne matchera pas. C’est pourquoi si on veut vraiment la matcher correctement nous devons ajouter : +
      + +
    • +([^<]|<[^l]|])*(|<| +
    • + +

      Oui, c’est un peu compliqué. Mais que se passe t’il lorsque la chaine de caractère que vous voulez matcher est encore plus longue que <li> ?

      +

      Voici un algorithme qui permet de résoudre ce problème aisément. Vous devez réduire ce problème au premier. C’est-à-dire celui avec une seule lettre :

      +
      +

      # transforme un simple caractère choisi aléatoirement # en un identifiant unique # (vous devez vérifier que l’identifier est VRAIMENT unique) # attention l’identifiant unique ne doit pas # contenir le caractère choisi. s/X/was_x/g s/Y/was_y/g

      +

      transforme la longue chaine de caractère

      +

      en un seul caractère

      +s/ +
    • +

      /X/g s/</li>/Y/g

      +

      Utilisation de la première méthode

      +

      s/X([^X]*)Y//g

      +

      Retransformation des lettres en chaines

      +

      de caractères

      +s/X/ +
    • +

      /g s/Y/</li>/g

      +

      retour des anciens caractères.

      +s/was_x/X/g s/was_y/Y/g
      +
    • + +

      Et ça fonctionne en seulement 9 lignes pour toute chaine de début et de fin. Cette solution fait un peu moins I AM THE GREAT REGEXP M45T3R, URAN00B, mais elle est mieux adaptée à mon avis. De plus, utiliser cette dernière solution prouve que vous maitrisez les expressions régulières. Simplement parce que vous savez qu’il est difficile de résoudre des problèmes de cette forme en utilisant seulement des expressions régulières.

      +
      +

      Je sais que j’ai utilisé une syntaxe HTML dans mon exemple. Mais dans l’utilisation réelle que j’en ai faite, je devais matcher entre en: et ::, sachant que parfois les chaines pouvaient se terminer par e::.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2010-02-16 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2010-02-18-split-a-file-by-keyword/index.html b/Scratch/fr/blog/2010-02-18-split-a-file-by-keyword/index.html new file mode 100644 index 0000000..6fd35f0 --- /dev/null +++ b/Scratch/fr/blog/2010-02-18-split-a-file-by-keyword/index.html @@ -0,0 +1,139 @@ + + + + + + YBlog - découper un fichier par mots clés + + + + + + + + + + + + + +
      + + +
      +

      découper un fichier par mots clés

      +
      +
      +
      +
      +

      Assez bizarrement, je n’ai trouvé aucun outil UNIX pour découper un fichier par mot clé. Alors j’en ai fait un en awk. Je le met ici principalement pour moi, mais ça peut toujours servir à quelqu’un d’autre. Le code suivant découpe un fichier pour chacune de ses ligne contenant le mot UTC.

      +
      + #!/usr/bin/env awk BEGIN{i=0;} /UTC/ { i+=1; FIC=sprintf(“fic.%03d”,i); } {print $0>>FIC} +
      + +

      En réalité, j’avais besoin de cet outils pour avoir un fichier par jour. Chaque ligne contenant UTC ayant le format suivant :

      +
      +Mon Dec  7 10:32:30 UTC 2009
      +
      + +

      J’en suis finallement arrivé au code suivant :

      +
      + #!/usr/bin/env awk BEGIN{i=0;} /UTC/ { date=$1$2$3; if ( date != olddate ) { olddate=date; i+=1; FIC=sprintf(“fic.%03d”,i); } } {print $0>>FIC} +
      + + +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2010-02-18 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2010-02-23-When-regexp-is-not-the-best-solution/index.html b/Scratch/fr/blog/2010-02-23-When-regexp-is-not-the-best-solution/index.html new file mode 100644 index 0000000..63d2b69 --- /dev/null +++ b/Scratch/fr/blog/2010-02-23-When-regexp-is-not-the-best-solution/index.html @@ -0,0 +1,165 @@ + + + + + + YBlog - Quand se passer des expressions régulières ? + + + + + + + + + + + + + +
      + + +
      +

      Quand se passer des expressions régulières ?

      +
      +
      +
      +
      +

      Les expressions régulières sont très utiles. Cependant, elles ne sont pas toujours la meilleure manière d’aborder certain problème autour des chaines de caractères. Et surtout quand les transformations que vous voulez accomplir sont simples.

      +

      Je voulais savoir comment récupérer le plus vite possible l’extension d’un nom de fichier. Il y a trois manière naturelle d’accomplir celà :

      +
      +

      # regexp str.match(/[^.]* / ); ext = &

      +

      split

      +

      ext=str.split(‘.’)[-1]

      +

      File module

      +ext=File.extname(str)
      +
      + +

      A première vue, je pensais que l’expression régulière serait plus rapide que le split parce qu’il pouvait y avoir plusieurs de . dans un nom de fichier. Mais la majorité du temps il n’y a qu’un seul point par nom de fichier. C’est pourquoi j’ai réalisé que le split serait plus rapide. Mais pas le plus rapide possible. Il y a une fonction qui est dédiée à faire ce travail dans un module standard de ruby ; le module File.

      +

      Voici le code pour faire un benchmark :

      +
      +

      #!/usr/bin/env ruby require ‘benchmark’ n=80000 tab=[ ‘/accounts/user.json’, ‘/accounts/user.xml’, ‘/user/titi/blog/toto.json’, ‘/user/titi/blog/toto.xml’ ]

      +puts “Get extname” Benchmark.bm do |x| x.report(“regexp:”) { n.times do str=tab[rand(4)]; str.match(/[^.]* / ); ext = &; end } x.report(" split:“) { n.times do str=tab[rand(4)]; ext=str.split(‘.’)[-1] ; end } x.report(” File:") { n.times do str=tab[rand(4)]; ext=File.extname(str); end } end +
      + +

      Et voici les résultats :

      +
      +Get extname
      +            user     system      total        real
      +regexp:  2.550000   0.020000   2.570000 (  2.693407)
      + split:  1.080000   0.050000   1.130000 (  1.190408)
      +  File:  0.640000   0.030000   0.670000 (  0.717748)
      +
      + +

      En conclusion, les fonction dédiées sont meilleures que votre façon de faire (la plupart du temps).

      +

      Chemin complet d’un fichier sans l’extension

      +
      +

      #!/usr/bin/env ruby require ‘benchmark’ n=80000 tab=[ ‘/accounts/user.json’, ‘/accounts/user.xml’, ‘/user/titi/blog/toto.json’, ‘/user/titi/blog/toto.xml’ ]

      +puts “remove extension” Benchmark.bm do |x| x.report(" File:“) { n.times do str=tab[rand(4)]; path=File.expand_path(str,File.basename(str,File.extname(str))); end } x.report(”chomp:") { n.times do str=tab[rand(4)]; ext=File.extname(str); path=str.chomp(ext); end } end +
      + +

      et voici les résultats :

      +
      +remove extension
      +          user     system      total        real
      + File:  0.970000   0.060000   1.030000 (  1.081398)
      +chomp:  0.820000   0.040000   0.860000 (  0.947432)
      +
      + +

      En conclusion du ce second benchmark. Un fonction simple est meilleure que trois fonctions dédiées. Pas de surprise, mais c’est toujours bien de savoir.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2010-02-23 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2010-03-22-Git-Tips/index.html b/Scratch/fr/blog/2010-03-22-Git-Tips/index.html new file mode 100644 index 0000000..b12c6ff --- /dev/null +++ b/Scratch/fr/blog/2010-03-22-Git-Tips/index.html @@ -0,0 +1,154 @@ + + + + + + YBlog - Astuces Git + + + + + + + + + + + + + +
      + + +
      +

      Astuces Git

      +
      +
      +
      +
      +

      Cloner de github à travers un pare-feu

      +

      La façon standard:

      +
      + git clone git@github.com:yogsototh/project.git +
      + +

      En utilisant le port HTTPS :

      +
      + git clone git+ssh://git@github.com:443/yogsototh/project.git +
      + +

      Cloner toutes les branches

      +

      git clone peut seulement récuper la branche master.

      +

      Si vous n’avez pas beaucoup de branches, vous pouvez simplement les clone le project et ensuite pour chacune d’entre elle lancer la commande suivante :

      +
      + git branch –track local_branch remote_branch +
      + +par exemple : +
      + $ git clone git@github:yogsototh/example.git $ git branch master * $ git branch -a master * remotes/origin/HEAD -> origin/master remotes/origin/experimental $ git branch –track experimental remotes/origin/experimental $ git branch master * experimental +
      + +

      Si vous avez beaucoup de branches il peut être utile d’utiliser le script/la longue ligne de commande suivant(e) :

      +
      +

      # first clone your project $ git clone git@github.com:yogsototh/project.git

      +

      copy all branches

      +$ zsh $ cd project $ for br in (gitbr − a); docasebr in remotes/*) print br; case{br:t} in master|HEAD) continue ;; *) git branch –track br: tbr ;; esac ;; esac done
      +
      + +

      Et toutes les branches seront récupérées en local.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2010-03-22 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2010-03-23-Encapsulate-git/index.html b/Scratch/fr/blog/2010-03-23-Encapsulate-git/index.html new file mode 100644 index 0000000..e19e77f --- /dev/null +++ b/Scratch/fr/blog/2010-03-23-Encapsulate-git/index.html @@ -0,0 +1,178 @@ + + + + + + YBlog - Encapsuler git + + + + + + + + + + + + + +
      + + +
      +

      Encapsuler git

      +
      +
      +
      +
      +

      Voici une solution pour conserver des branches divergentes avec git. Parce qu’il est facile de merger par erreur, je propose un script qui encapsule le comportement de git pour interdire certains merges dangereux. Mais qui permet aussi de faire des merges en cascades de la racines vers les autres branches.

      +

      Se prémunir contre les erreurs

      +

      Je travaille sur un projet dans lequel certaines de mes branches git doivent rester divergentes. Et les divergences devraient aller en s’accentuant.

      +

      J’utilise aussi certaines branches qui contiennent la partie commune de ces projets.

      +

      Disons que j’ai les branches :

      +
        +
      • master: commune à toutes les branches
      • +
      • dev: branche instable pour le développement
      • +
      • client: Branche commune à plusieurs clients
      • +
      • clientA: le projet spécialisé pour le client A
      • +
      • clientB: le projet spécialisé pour le client B
      • +
      +

      Voilà comment je souhaiterai que ça fonctionne

      +

      Dynamic branching

      +

      Et plus précisément la hiérarchie des branches :

      +

      Branch hierarchy

      +

      Une flèche de A vers B signifie que l’on peut merger A dans B. S’il n’y a pas de flèche de A vers B cela signifie qu’il est interdit de merger A dans B. Voici le code ruby correspondant :

      +
      + $architecture={ :master => [ :dev, :client ], :dev => [ :master ], :client => [ :clientA, :clientB ] } +
      + +

      :master => [ :dev, :client ] signifie que l’on peut merger la branche master dans la branche dev et la branche client.

      +

      Je fait une erreur si je tape git checkout master && git merge clientA. C’est pour éviter ça que j’ai fait un script pour encapsuler le comportement de git.

      +

      Mais ce script fait bien plus que ça. Il fait des merges en cascade de la racine vers les feuilles avec l’acion allmerges.

      +
      + git co dev && git merge master git co client && git merge master git co clientA && git merge client git co clientB && git merge client +
      + +

      Je peux ainsi mettre à jour toutes les branches. L’algorithme ne boucle pas même s’il y a des cycles dans la structure des branches.
      Le voici :

      +
      +

      #!/usr/bin/env ruby # encoding: utf-8

      +

      architecture

      +

      +

      master <-> dev

      +

      master -> client

      +

      clien -> clientA | clientB

      +

      +

      merge using two of these branches should be

      +

      restricted to these rules

      +

      merge to one of these branch and an unknown one should

      +

      raise a warning, and may the option to add this new branch

      +

      to the hierarchy

      +

      $architecture={ :master => [ :dev, :client ], :dev => [ :master ], :client => [ :clientA, :clientB ] }

      +

      def get_current_branch() (git branch --no-color | awk '$1 == "*" {print $2}').chop.intern end

      +

      if ARGV.length == 0 puts %{usage: $0:t [git_command or local_command]

      +

      local commands: allmerges: merge from top to down} exit 0 end

      +

      require ‘set’ knownbranches = Set. newarchitecture.each do |k,v| $known_branches.add(k) v.each { |b| $known_branches.add(b) } end

      +

      def rec_merge(branch) if architecture[branch]. nil? returnendarchitecture[branch].each do |b| if flag. haskey? (b. tos + branch. tos)nextendflagname = branch. tos + b. tosifflag.has_key?(flagname) next end if system %{eng checkout #{b}} if get_current_branch != b puts “Can’t checkout to #{b}” exit 2 end if system %{eng merge #{branch}} $flag[flagname]=true rec_merge(b) else exit 1 end else exit 1 end end end

      +

      def do_all_merges puts ‘Will merge from father to sons’ current_branch=get_current_branch $flag={} rec_merge(:master) system %{git co #{current_branch}} end

      +

      def do_merge current_branch=get_current_branch src_branch=ARGV[1].intern puts %{do_merge: #{src_branch} => #{current_branch}} if knownbranches. include? (currentbranch)ifknown_branches.include?(src_branch) if architecture. haskey? (srcbranch)andarchitecture[src_branch].include?(current_branch) system %{git merge #{src_branch}} else puts %{Forbidden merge: #{src_branch} => #{current_branch}} end else puts %{Warning! #{src_branch} not mentionned in rb configuration} sleep 2 f system %{git merge #{src_branch}} puts %{Warning! #{src_branch} not mentionned in rb configuration} end end end

      +case ARGV[0] when ‘allmerges’ then do_all_merges when ‘merge’ then do_merge else system %{git #{ARGV.join(‘’)}} end
      +
      + +

      Pour que ça fonctionne il vous suffit de copier eng dans un répertoire présent dans votre PATH.

      +

      Bien entendu, il faut essayer de faire aussi peu que possible des cherry-pick et des rebase. Ce script a pour but de s’intégrer avec les workflow basé sur les pull et merge.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2010-03-23 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2010-05-17-at-least-this-blog-revive/index.html b/Scratch/fr/blog/2010-05-17-at-least-this-blog-revive/index.html new file mode 100644 index 0000000..43d1126 --- /dev/null +++ b/Scratch/fr/blog/2010-05-17-at-least-this-blog-revive/index.html @@ -0,0 +1,144 @@ + + + + + + YBlog - Je reviens à la vie ! + + + + + + + + + + + + + +
      + + +
      +

      Je reviens à la vie !

      +
      +
      +
      +
      +

      Bonjour à tous !

      +
      +

      …plus on retarde quelque chose, plus il devient difficile de s’y mettre… {: cite=“http://www.madore.org/~david/weblog/2010-05.html#d.2010-05-12.1752” }

      +
      +

      Je devais écrire d’autres articles pour ce blog. J’ai noté plein d’idées dans mes todolist. Mais j’avais pas mal d’autres choses à faire. Et jusqu’ici, j’ai toujours dit «je le ferai plus tard». Ce qui m’a fait agir, c’est la petite réflexion que j’avais lu une fois. > Arrétez d’écrire des TODO dans votre code est faites le maintenant !
      > Vous serez surpris de l’efficacité de cette mesure.

      +

      En résumé : > Just do it! ou Juste fait le comme auraient dit les nuls.

      +

      Finallement j’écrirais plus souvent ces derniers temps.

      +

      Qu’ai-je fait ?

      +

      J’ai fini une application web pour gridpocket(c).

      +

      J’ai aussi fini d’adapter mon site à la dernière version de nanoc. La partie difficile étant d’avoir un système qui permet de gérer plusieurs langues. Je pense que j’écrirai les détails dans un article.

      +

      J’ai aussi une vrai vie. J’ai passé quelques moments agréables avec ma famille et des amis.

      +

      J’ai travaillé avec luc sur un framework REST/JSON orienté API écrit en ruby. Il fonctionne assez bien, avec très peu de bug jusqu’ici. Nous espérons pouvoir écrire un tutoriel de todolist. Peut-être en deux ou trois posts. Ce framework n’est pas encore public. Il le deviendra certainement lorsque nous aurons fini d’écrire une application web et que nous aurons fait un joli site pour lui.

      +

      Finallement voilà un partie des choses que je dois faire :

      +
        +
      • finir de faire un service web public (je pense que ça peut être populaire) ;
      • +
      • finir d’écrire l’application iPhone associée ;
      • +
      • finir de publier notre framework pour créer des services web ;
      • +
      • publier des articles sur ce blog ;
      • +
      • publier le code source de ce blog sur github.
      • +
      +

      Certaines choses ne seront peut-être jamais finie. Mais simplement parce qu’elles ne dépendent pas complètement de moi.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2010-05-17 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2010-05-19-How-to-cut-HTML-and-repair-it/index.html b/Scratch/fr/blog/2010-05-19-How-to-cut-HTML-and-repair-it/index.html new file mode 100644 index 0000000..27495f7 --- /dev/null +++ b/Scratch/fr/blog/2010-05-19-How-to-cut-HTML-and-repair-it/index.html @@ -0,0 +1,189 @@ + + + + + + YBlog - Comment réparer un XML coupé ? + + + + + + + + + + + + + +
      + + +
      +

      Comment réparer un XML coupé ?

      +
      +
      +
      +
      +

      Sur ma page d’accueil vous pouvez voir la liste des mes derniers articles avec le début de ceux-ci. Pour arriver à faire ça, j’ai besoin de couper le code XHTML de mes pages en plein milieu. Il m’a donc fallu trouver un moyen de les réparer.

      +

      Prenons un exemple :

      +
      <div class="corps">
      +    <div class="intro">
      +        <p>Introduction</p>
      +    </div>
      +    <p>The first paragraph</p>
      +    <img src="/img/img.png" alt="an image"/>
      +    <p>Another long paragraph</p>
      +</div>
      +

      Après avoir coupé, j’obtiens :

      +
      <div class="corps">
      +    <div class="intro">
      +        <p>Introduction</p>
      +    </div>
      +    <p>The first paragraph</p>
      +    <img src="/img/im
      +

      En plein milieu d’un tag <img> !

      +

      En réalité, ce n’est pas si difficile que celà peut paraître au premier abord. Le secret réside dans le fait de comprendre que l’on n’a pas besoin de conserver la structure complète de l’arbre pour le réparer, mais seulement la liste des parents non fermés.

      +

      Pour notre exemple, juste après le paragraphe first paragraph nous n’avons qu’à fermer un div pour la classe corps et le XML est réparé. Bien entendu, quand on est dans le cas où un tag est coupé au milieu, on a qu’à remonté juste avant le début de ce tag corrompu.

      +

      Donc, tout ce que nous avons à faire, c’est d’enregistrer la liste des parents dans une pile. Supposons que nous traitions le premier exemple complètement. La pile passera par les états suivants :

      +
      []           
      +[div]           <div class="corps">
      +[div, div]          <div class="intro">
      +[div, div, p]           <p>
      +                            Introduction
      +[div, div]              </p>
      +[div]               </div>
      +[div, p]            <p>
      +                        The first paragraph
      +[div]               </p>
      +[div]               <img src="/img/img.png" alt="an image"/>
      +[div, p]            <p>
      +                        Another long paragraph
      +[div]               </p>
      +[]              </div>
      +

      L’algorithme est alors très simple : ~~~~~~ {.html} let res be the XML as a string ; read res and each time you encouter a tag: if it is an opening one: push it to the stack else if it is a closing one: pop the stack.

      +

      remove any malformed/cutted tag in the end of res for each tag in the stack, pop it, and write: res = res + closed tag

      +

      return res ~~~~~~

      +

      Et res contiend le XML réparé.

      +

      Finallement, voici le code en ruby que j’utilise. La variable xml contient le XML coupé.

      +
      # repair cutted XML code by closing the tags
      +# work even if the XML is cut into a tag.
      +# example:
      +#    transform '<div> <span> toto </span> <p> hello <a href="http://tur'
      +#    into      '<div> <span> toto </span> <p> hello </p></div>'
      +def repair_xml( xml )
      +    parents=[]
      +    depth=0
      +    xml.scan( %r{<(/?)(\w*)[^>]*(/?)>} ).each do |m|
      +        if m[2] == "/"
      +            next
      +        end
      +        if m[0] == "" 
      +            parents[depth]=m[1]
      +            depth+=1
      +        else
      +            depth-=1
      +        end
      +    end
      +    res=xml.sub(/<[^>]*$/m,'')
      +    depth-=1
      +    depth.downto(0).each { |x| res<<= %{</#{parents[x]}>} }
      +    res
      +end
      +

      Je ne sais pas si ce code pourra vous être utile. Par contre le raisonnement pour y parvenir mérite d’être connu.

      +
      + +
      + + RSS + + + + + + +
      + +
      +
      +
      +
      +

      Comments

      +
      + + + comments powered by Disqus +
      +
      +
      + Published on 2010-05-19 +
      + +
      + Yann Esposito© +
      +
      + Done with + Vim + & + Hakyll +
      +
      +
      + +
      + + + + + + + + diff --git a/Scratch/fr/blog/2010-05-24-Trees--Pragmatism-and-Formalism/index.html b/Scratch/fr/blog/2010-05-24-Trees--Pragmatism-and-Formalism/index.html new file mode 100644 index 0000000..af9e1ea --- /dev/null +++ b/Scratch/fr/blog/2010-05-24-Trees--Pragmatism-and-Formalism/index.html @@ -0,0 +1,313 @@ + + + + + + YBlog - Arbres ; Pragmatisme et Formalisme + + + + + + + + + + + + + +
      + + +
      +

      Arbres ; Pragmatisme et Formalisme

      +
      +
      +
      +
      +
      + +

      tlpl:  :

      +
        +
      • J’ai essayé de programmer un simple filtre ;
      • +
      • J’ai été bloqué pendant deux jours ;
      • +
      • J’ai arrêté de penser comme un robot ;
      • +
      • J’ai utilisé un papier et un stylo ;
      • +
      • J’ai fait un peu de maths ;
      • +
      • J’ai résolu le problème en 10 minutes ;
      • +
      • Conclusion: Pragmatisme n’est pas : «n’utilisez jamais la théorie». +
      +

    • +
    +

    Résumé (plus long que le tlpl: )

    +

    Je devais résoudre un problème à mon travail. Au début cela semblait assez facile. J’ai donc commencé à programmer tout de suite. Je suis alors entré dans un cercle infernal d’essais et de réparations. Voilà à quoi ressemblait cet étrange état de boucle infini :

    +
    +

    – Plus que ça a réparer et ça devrait être bon.
    – Très bien, maintenant ça doit marcher.
    – Oui !!
    – Ah mince! J’ai oublié ce détail…
    répéter jusqu'à la mort

    +
    +

    Après deux jours à me prendre pour Sisyphe, je me suis arrêté pour repenser le problème. J’ai pris un stylo et une feuille de papier. Je me suis souvenu de de ce que j’avais appris sur les arbres pendant mon doctorat. Finalement, le problème fut résolu en moins de 20 minutes.

    +

    Je pense que la leçon à retenir de cette expérience est de se souvenir que la méthodologie la plus efficace pour résoudre ce problème pragamtique était la méthode théorique. Ça ne signifie pas que la méthode théorique est toujours la meilleure, mais en tout cas, il ne faut pas l’écarter.

    +
    +

    L’anecdote

    +

    Apparemment 90% des programmeurs sont incapable de programmer une recherche binaire sans faire de bug. L’algorithme est pourtant connu et facile à comprendre. Cependant, il est difficile à programmer sans bug. J’ai participé à ce concours. Vous pouvez voir les résultats ici1. J’ai dû faire face à un problème similaire à mon travail. Il paraissait simple au départ. Transformer un xml d’un format à un autre.

    +

    Voici le format général du xml source :

    +
    <rubrique>
    +    <contenu>
    +        <tag1>value1</tag1>
    +        <tag2>value2</tag2>
    +        ...
    +    </contenu>
    +    <enfant>
    +        <rubrique>
    +            ...
    +        </rubrique>
    +        ...
    +        <rubrique>
    +            ...
    +        </rubrique>
    +    </enfant>
    +</menu>
    +

    et le format d’arrivé est celui-ci :

    +
    <item name="Menu0">
    +    <value>
    +        <item name="menu">
    +            <value>
    +                <item name="tag1">
    +                    <value>value1</value>
    +                </item>
    +                <item name="tag2">
    +                    <value>value2</value>
    +                </item>
    +                ...
    +                <item name="menu">
    +                    <value>
    +                        ...
    +                    </value>
    +                    <value>
    +                        ...
    +                    </value>
    +                </item>
    +            </value>
    +        </item>
    +    </value>
    +</item>
    +

    À première vue, cela m’a paru simple. J’étais certain de pouvoir y arriver en me fixant les règles suivantes :

    +
      +
    1. ne pas utiliser xslt ;
    2. +
    3. ne pas utiliser de parseur xml ;
    4. +
    5. résoudre le problème en utilisant un simple script perl
    6. +
    +

    Vous pouvez essayer si vous le souhaitez. Si vous attaquez ce problème directement en écrivant le programme, ce ne sera certainement pas si simple. Je peux le dire, parce que c’est ce que j’ai fait. Et je dois dire que j’ai perdu une journée de travail complète en m’y prenant de la sorte. En réalité, il y avait pas mal de petits détails dont je ne parle pas qui m’ont induis en erreur et qui m’ont fait perdre encore plus de temps.

    +

    Pourquoi étais-je incapable de résoudre ce problème si simple en aparence ?

    +

    Voici comment je m’y suis pris :

    +
      +
    1. Réfléchir
    2. +
    3. Écrire le programme
    4. +
    5. Essayer le programme
    6. +
    7. Vérifier les résultats
    8. +
    9. Trouver un bug
    10. +
    11. Résoudre le bug
    12. +
    13. Reprendre à l’étape 3
    14. +
    +

    Il s’agissait d’une méthode de travail standard pour un ingénieur en informatique. L’erreur venait de la première étape. J’ai d’abord pensé à comment résoudre le problème mais avec des yeux d’ingéinieur pragmatique. Je me suis simplement dit :

    +
    +

    Ça à l’air de pouvoir se résouvre avec un petit script de search&replace en perl Commençons à écrire le code maintenant.

    +
    +

    C’est la deuxième phrase qui est complètement fausse. Parce que j’avais mal commencé et que cette méthodologie de travail ne fonctionne pas lorsque l’on part vraiment mal.

    +

    Réfléchir

    +

    Après un certain temps, j’ai arrêté de programmer et je me suis dit : «Maintenant, ça suffit !». J’ai pris une feuille et un stylo et j’ai commencé à dessiner des arbres.

    +

    J’ai commencer par simplifier un peu en enlevant le maximum de verbiage. Tout d’abord en renommant <item name="Menu"> par un simple M par exemple. J’ai obtenu quelque chose comme :

    +

    subgraph cluster_x { node [label=“C”] C_x ; node [label=“E”] E_x ; node [label=“a1”] tag1_x ; node [label=“a2”] tag2_x ; node [label=“R”, color=“#333333”, fillcolor=“#333333”, fontcolor=“white”] R_x ; R_x -> C_x; C_x -> tag1_x ; C_x -> tag2_x ; R_x -> E_x ; } subgraph cluster_y { node [label=“C”] C_y ; node [label=“E”] E_y ; node [label=“a1”] tag1_y ; node [label=“a2”] tag2_y ; node [label=“R”, color=“#333333”, fillcolor=“#333333”, fontcolor=“white”] R_y ; R_y -> C_y; C_y -> tag1_y ; C_y -> tag2_y ; R_y -> E_y ; } subgraph cluster_z { node [label=“C”] C_z ; node [label=“E”] E_z ; node [label=“a1”] tag1_z ; node [label=“a2”] tag2_z ; node [label=“R”, color=“#333333”, style=“filled”, fillcolor=“#333333”, fontcolor=“white”] R_z ; R_z -> C_z; C_z -> tag1_z ; C_z -> tag2_z ; R_z -> E_z ; } E_x -> R_y ; E_x -> R_z ;

    +

    +

    et

    +

    subgraph cluster_x { node [label=“M”] E_x ; node [label=“a1”] tag1_x ; node [label=“V”] value_tag1_x ; node [label=“a2”] tag2_x ; node [label=“V”] value_tag2_x ; node [label=“V”, color=“#333333”, fillcolor=“#333333”, fontcolor=“white”] R_x ; R_x -> value_tag1_x -> tag1_x ; R_x -> value_tag2_x -> tag2_x ; R_x -> E_x ; } subgraph cluster_y { node [label=“M”] E_y ; node [label=“a1”] tag1_y ; node [label=“V”] value_tag1_y ; node [label=“a2”] tag2_y ; node [label=“V”] value_tag2_y ; node [label=“V”, color=“#333333”, fillcolor=“#333333”, fontcolor=“white”] R_y ; R_y -> value_tag1_y -> tag1_y ; R_y -> value_tag2_y -> tag2_y ; R_y -> E_y ; } subgraph cluster_z { node [label=“M”] E_z ; node [label=“a1”] tag1_z ; node [label=“V”] value_tag1_z ; node [label=“a2”] tag2_z ; node [label=“V”] value_tag2_z ; node [label=“V”, color=“#333333”, fillcolor=“#333333”, fontcolor=“white”] R_z ; R_z -> value_tag1_z -> tag1_z ; R_z -> value_tag2_z -> tag2_z ; R_z -> E_z ; } E_x -> R_y ; E_x -> R_z ;

    +

    +

    Puis, je me suis fait la réflexion suivante :

    +

    Dans les distances d’éditions sur les arbres, chaque opération atomique correspond à un simple search and replace sur mon fichier xml source2. On considère trois opérations atomiques sur les arbres :

    +
      +
    • substitution: renommer un nœud
    • +
    • insertion: ajouter un nœud
    • +
    • délétion: supprimer un nœud
    • +
    +

    Une des particularité avec les transformations sur les arbres est celle-ci : supprimer un nœud et tous ses enfants deviendront les enfants du père de ce nœud.

    +

    Un exemple:

    +
    +r - x - a
    +  \   \
    +   \    b
    +    y - c   
    +
    + +

    Si vous supprimez le nœud x, vous obtenez

    +
    +    a
    +  /
    +r - b
    +  \
    +    y - c   
    +
    + +

    Et regardez ce que ça implique quand on l’écrit en xml :

    +
    <r>
    +  <x>
    +    <a>value for a</a>
    +    <b>value for b</b>
    +  </x>
    +  <y>
    +    <c>value for c</c>
    +  </y>
    +</r>
    +

    Alors supprimer tous les nœuds x revient à faire passer le xml à travers le filtre suivant :

    +
    s/<\/?x>//g
    +

    Par conséquent, s’il existe un transducteur déterministe à un état qui permet de transformer mes arbres ; je suis capable de transformer le xml d’un format à l’autre en utilisant une simple liste de search and replace.

    +

    Solution

    +

    Transformer cet arbre :

    +
    +R - C - tag1
    +  \   \
    +   \    tag2
    +    E -- R - C - tag1
    +      \   \    \
    +       \   \     tag2
    +        \    E ...
    +         R - C - tag1 
    +           \    \
    +            \     tag2
    +             E ...
    +
    + +

    en celui-ci :

    +
    +                tag1
    +              /
    +M - V - M - V - tag2      tag1
    +              \         / 
    +                M --- V - tag2
    +                  \     \ 
    +                   \      M
    +                    \     tag1
    +                     \  / 
    +                      V - tag2
    +                        \ 
    +                          M
    +
    + +

    peut-être fait en utilisant le transducteur déterministe à un état suivant:

    +
    +

    C -> ε
    E -> M
    R -> V

    +
    +

    Ce qui peut-être traduit par les simples directives Perl suivantes :

    +
    s/C//g
    +s/E/M/g
    +s/R/V/g
    +

    Une fois adapté au xml cela devient :

    +
    s%</?contenu>%%g
    +s%<enfant>%<item name="menu">%g
    +s%</enfant>%</item>%g
    +s%<rubrique>%<value>%g
    +s%</rubrique>%</value>%g
    +

    Et c’est tout.

    +

    Conclusion

    +

    Même si cela peut sembler paradoxal, parfois la solution la plus efficace à un problème pragmatique est d’utiliser une méthodologie théorique.

    +
    +
    +
      +
    1. Normalement, je fais parti des 10% qui ont fourni une implémentation sans bug.

    2. +
    3. J’ai programmé un outil qui calcule automatiquement le poids de chaque élément des matrices d’édition à partir de données.

    4. +
    +
    +

    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-05-24 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + + + + + + + + + + diff --git a/Scratch/fr/blog/2010-06-14-multi-language-choices/index.html b/Scratch/fr/blog/2010-06-14-multi-language-choices/index.html new file mode 100644 index 0000000..1acbeac --- /dev/null +++ b/Scratch/fr/blog/2010-06-14-multi-language-choices/index.html @@ -0,0 +1,152 @@ + + + + + + YBlog - choix liés à l'écriture dans plusieurs langues + + + + + + + + + + + + + +
    + + +
    +

    choix liés à l'écriture dans plusieurs langues

    +
    +
    +
    +
    +

    Je traduis la plupart de mes articles pour qu’ils soient disponibles en français et en anglais. La façon que l’on m’a conseillé était d’avoir un fichier par langue. En général ça donne ça.

    +
    +Bonjour, 
    +
    +voici un exemple de texte en français.
    +[image](url)
    +
    + +
    +Hello, 
    +
    +here is an example of english text.
    +[image](url)
    +
    + +

    Cette façon de traduire vous impose une certaine façon de traduire. D’abord écrire entièrement le texte dans une langue, puis copier le fichier et enfin retraduire dans une nouvelle langue.

    +

    Le problème, c’est que très souvent, les articles ont des parties communes non négligeables. Par exemple, les images, les codes sources, etc… Lorsque je m’aperçoit que j’ai fait une erreur dans ces parties communes ça m’oblige à refaire deux fois la même manipulation. Sauf que comme il m’arrive d’être distrait, il peut y avoir pas mal d’aller-retours.

    +

    C’est pourquoi, j’ai plutôt opté pour une autre solution. J’utilise des tags sur un seul fichier. En fin de compte, mes fichiers ressemblent à :

    +
    + fr:   Bonjour, 
    + en:   Hello, 
    +
    + en:   here is an example of english text.
    + fr:   voici un exemple de texte en français.
    +[image](url)
    +
    + +

    Comme j’édite mes fichier avec vim, il m’est très facile d’ajouter ces fr: ou en: en début de ligne à l’aide du très utile C-v. Par contre nanoc a été conçu pour être utilisé par une seule langue. Précédemment, j’avais utilisé les capacité de nanoc pour séparer les langues. Mais finalement, il s’avère bien plus simple de faire un pré-traitement qui nettoie mes fichiers et en fait deux copie qui seront ensuite gérées par nanoc.

    +

    Vous pouvez récupérer les sources de mon blog (sans tous les articles) à l’adresse suivante github.com/yogsototh/Scratch. J’écrirais un article pour savoir comment l’utiliser facilement. J’ai en effet ajouté beaucoup de scripts et de librairies.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-06-14 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-06-15-Get-my-blog-engine/index.html b/Scratch/fr/blog/2010-06-15-Get-my-blog-engine/index.html new file mode 100644 index 0000000..c95e9a5 --- /dev/null +++ b/Scratch/fr/blog/2010-06-15-Get-my-blog-engine/index.html @@ -0,0 +1,187 @@ + + + + + + YBlog - Récupérez mon système de blog + + + + + + + + + + + + + +
    + + +
    +

    Récupérez mon système de blog

    +
    +
    +
    +
    +

    J’ai publié une version light de mon système de blog hier soir. Par light il faut comprendre avec un CSS plus épuré et plus portable (sans les bords ronds). Vous pouvez le récupérer sur github.com.

    +

    Que pouvez-vous attendre de ce système de blog ?

    +
      +
    • Tous les avantages liés à nanoc ;
    • +
    • Facilité de la gestion de plusieurs langues ;
    • +
    • coloration syntaxique des codes sources pour la plupart des languages ;
    • +
    • commentaires gérés avec intenseDebate de façon asynchrone ;
    • +
    • très portable avec ou sans javascript, XHTML Strict 1.0 / CSS3 ;
    • +
    • écrivez vos entrées au format Markdown (pas de HTML) ;
    • +
    • des améliorations typographiques (pas de ‘:’ en début de ligne en Français par exemple),
    • +
    • entrez directement le code de graphes qui se génèreront automatiquement en image à l’aide de Graphviz.
    • +
    +

    Pour vous donner une idée plus précise, voici la documentation que j’ai faite (en anglais) pour accompagner le code.

    +
    +

    Main Documentation Page

    +

    Cette page est seulement en anglais désolé.

    +

    Use It NOW!

    +

    Once installed (follow the README.md instructions).

    +
    $ cd /root/of/nanoc3_blog
    +$ ./task/new_blog_entry Title of the blog
    +$ vi latest.md
    +$ ./task/recompile
    +

    Now your website reside into the output directory.

    +
    +

    Documentation

    +

    Useful things to know

    +

    Multi-language

    +

    All files in multi are processed and copied in the content directory. For each file in multi, each line starting by ‘fr:’ are copied (without the fr: into the content/html/fr/ tree, but not into the content/html/en tree. File not starting by fr: or en: are copied in each destinations.

    +

    If you want to add another language, you’ll have to modify tasks/config, and config.yaml, create a content/html/xx where xx is the language code.

    +

    Edition & Rendering

    +

    additional keywords

    +

    You can separate multi content div using the: n``ewcorps directive (see examples).

    +

    You can create div using b``egindiv(classname), e``nddiv. (See some existing blog entries for example). Use the class intro for the abstract part.

    +

    You can create nice description table using <``desc> (See source code for example).

    +

    Typography

    +

    In French all ‘:’, ‘;’, ‘!’ and ‘?’ are preceded automatically by &nbsp. This enable not to have a line starting by a single special character.

    +

    You can use small caps using <sc> tags.

    +
      +
    • (c``) is replaced by (c).
    • +
    • (r``) is replaced by (r).
    • +
    • <``- is replaced by <-.
    • +
    • -``> is replaced by ->.
    • +
    +

    source code

    +

    To write source code you should use the following format:

    +

    ~~~~~~ {.html} ~~~~~~ {.ruby} The code ~~~~~~

    +

    The file attribute is not required.

    +

    blog

    +

    If you want to make really long blog post, you can separate them into many files. To accomplish that, you simply have to make your files like:

    +
    +multi/blog/2010-06-01-the-title.md
    +multi/blog/2010-06-01-the-title/second_part.md
    +multi/blog/2010-06-01-the-title/third_part.md
    +
    + +

    mobileme

    +

    All files are intended to be generated into the output/Scratch directory. This was made like that to work nicely with iWeb organisation of websites.

    + +

    The order of post is done using the menupriority meta-data in the header of the files.

    +

    You can hide some file from the menu by setting: isHidden: true in the header.

    +

    Details

    +

    To know more about this blog engine, you should look at nanoc project.

    +

    Then look at the files inside your project:

    +

    README.md : readme for the project (used by github) :: latest.md : symbolic link to the last blog entry :: multi/ : Directory containing multi-language articles :: tasks/ : scripts for website live :: config.yaml : global configuration file :: Rules : generation rules :: content/ : content files processed by nanoc :: layouts/ : erb templates :: lib/ : ruby libraries used to process files :: output/ : website :: Rakefile : not mandatory for this blog ::

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-06-15 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-06-17-hide-yourself-to-analytics/index.html b/Scratch/fr/blog/2010-06-17-hide-yourself-to-analytics/index.html new file mode 100644 index 0000000..73f6c92 --- /dev/null +++ b/Scratch/fr/blog/2010-06-17-hide-yourself-to-analytics/index.html @@ -0,0 +1,175 @@ + + + + + + YBlog - Se cacher de ses statistiques web + + + + + + + + + + + + + +
    + + +
    +

    Se cacher de ses statistiques web

    +
    +
    +
    +
    +

    Voici un moyen très simple de ne plus être comptabilisé dans les visites de son propre site. Tout d’abord, vous devriez jeter un coup d’œil sur comment je gère les systèmes de récupération de statistiques. Je centralise tout dans un seul fichier javascript ce qui facilite le travail.

    +

    Cette méthode nécessite l’utilisation de jquery-cookie.

    +

    Avant de comptabiliser les visites, je vérifie que la clé admin n’est pas utilisée dans mes cookies.

    +
        var admin = $.cookie('admin');
    +    if (! admin) {
    +        // put your analytics code here
    +    } else {
    +        console.log("[WARNING] you're HIDDEN to analytics");
    +    }
    +

    et il suffit de créer deux fichier html. Un pour se cacher :

    +
    <?xml version="1.0" encoding="utf-8"?>
    +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    +        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    +<html xmlns="http://www.w3.org/1999/xhtml" lang="fr" xml:lang="fr">
    +    <head>
    +        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    +        <script type="text/javascript" src="jquery.js"></script>
    +        <script type="text/javascript" src="jquery.cookie.js"></script>
    +        <script>
    +            $(document).ready(function(){
    +                $.cookie('admin',1);
    +                $('#info').html('Analytics can no more see you.')
    +            });
    +        </script>
    +        <title>Hide to analytics</title>
    +    </head>
    +    <body>
    +        <div id="info"></div> 
    +    </body>
    +</html>
    +

    et un autre pour redevenir visible (ça peut être utile) :

    +
    <?xml version="1.0" encoding="utf-8"?>
    +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    +        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    +<html xmlns="http://www.w3.org/1999/xhtml" lang="fr" xml:lang="fr">
    +    <head>
    +        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    +        <script type="text/javascript" src="jquery.js"></script>
    +        <script type="text/javascript" src="jquery.cookie.js"></script>
    +        <script>
    +            $(document).ready(function(){
    +                $.cookie('admin',null);
    +                $('#info').html('Analytics can see you.')
    +            });
    +        </script>
    +        <title>Hide to analytics</title>
    +    </head>
    +    <body>
    +        <div id="info"></div> 
    +    </body>
    +</html>
    +

    Maintenant en accédant à ces fichiers depuis votre navigateur vous pouvez disparaître des systèmes d’analyses ou bien être considéré comme tous les autres individus. Pensez à accéder à ces fichiers depuis tous les navigateurs que vous utilisez et vos visites ne seront plus comptabilisées.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-06-17 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-06-17-track-events-with-google-analytics/index.html b/Scratch/fr/blog/2010-06-17-track-events-with-google-analytics/index.html new file mode 100644 index 0000000..46d016f --- /dev/null +++ b/Scratch/fr/blog/2010-06-17-track-events-with-google-analytics/index.html @@ -0,0 +1,177 @@ + + + + + + YBlog - Analyser les clicks sur votre Site + + + + + + + + + + + + + +
    + + +
    +

    Analyser les clicks sur votre Site

    +
    +
    +
    +
    +

    Voici comment analyser tous les clics que font vos utilisateurs sur votre blog en incluant google analytics de façon asynchrone.

    +

    Dans le html, il faut utiliser jQuery et un fichier que j’ai appelé yga.js :

    +
        <script type="text/javascript" src="jquery.js"></script>
    +    <script type="text/javascript" src="yga.js"></script>
    +

    Voici le contenu du fichier yga.js :

    +
    $(document).ready( function() {
    +    // add an event to all link for google analytics
    +    $('a').click(function () {
    +        // tell analytics to save event
    +        try {
    +            var identifier=$(this).attr('id') ;
    +            var href=$(this).attr('href')
    +            var label="";
    +            if ( typeof( identifier ) != 'undefined' ) {
    +                label=label+'[id]:'+identifier
    +                category='JSLink'
    +            }
    +            if ( typeof( href ) != 'undefined' ) {
    +                label=label+' [href]:'+href
    +                if ( href[0] == '#' ) {
    +                    category='Anchor';
    +                } else {
    +                    category='Link';
    +                }
    +            }
    +            _gaq.push(['_trackEvent', category, 'clicked', label]);
    +            // console.log('[tracked]: ' + category + ' ; clicked ; ' + label );
    +        }
    +        catch (err) {
    +            console.log(err);
    +        }
    +
    +        // pause to allow google script to run
    +        var date = new Date();
    +        var curDate = null;
    +        do {
    +            curDate = new Date();
    +        } while(curDate-date < 300);
    +    });
    +});
    +
    +var _gaq = _gaq || [];
    +_gaq.push(['_setAccount', 'UA-XXXXXXXX-1']);
    +_gaq.push(['_trackPageview']);
    +
    +(function() {
    + var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    + ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    + var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
    + })();
    +

    Remplacez le : UA-XXXXXXXX-1 par votre code google analytics. Maintenant l’installation est finie.

    +

    Pour l’utiliser il suffit de se rendre dans google analytics rubrique Content puis Event Tracking comme sur la capture d’écran suivante :

    +

    +

    Joyeuse inspection du comportement de vos utilisateurs.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-06-17 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-06-19-jQuery-popup-the-easy-way/index.html b/Scratch/fr/blog/2010-06-19-jQuery-popup-the-easy-way/index.html new file mode 100644 index 0000000..9c66ec4 --- /dev/null +++ b/Scratch/fr/blog/2010-06-19-jQuery-popup-the-easy-way/index.html @@ -0,0 +1,149 @@ + + + + + + YBlog - Comment faire des popups en jQuery rapidement + + + + + + + + + + + + + +
    + + +
    +

    Comment faire des popups en jQuery rapidement

    +
    +
    +
    +
    +

    Voici une façon simple et rapide pour faire des popups avec jQuery.

    +
    // --- code popup ---
    +function openPopup() {
    +    $(this).clone(false).appendTo($("#_code"));
    +    $("#_code").show();
    +}
    +
    +function closePopup() {
    +    $("#_code").html("");
    +    $("#_code").hide();
    +}
    +
    +function initCode() {
    +    $(".code").click(openPopup);
    +    $(".code").css({cursor: "pointer"});
    +    $('body').append('<div id="_code"></div>');
    +    $('#_code').css( { 'text-align': "justify", position: "fixed", 
    +                        left:0, top:0, width: "100%", height: "100%", 
    +                        "background-color": "rgba(0, 0, 0, 0.8)", 'z-index':2000, 'padding':'3px'} );
    +    $('#_code').hide();
    +    $('#_code').click(closePopup);
    +}
    +// --- end of code popup section ---
    +

    Que fait ce code ?

    +

    Au chargement de la page je crée un div grand comme toute la page avec un fond légèrement transparent que je cache. Je fais bien attention à son z-index pour qu’il soit devant tout le reste.

    +

    Puis lorsque l’on clique sur un div de class code, je recopie le contenu de celui-ci dans le grand div que je rend visible. Très simple mais très efficace. Pas besoin d’utiliser un plugin jQuery.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-06-19 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-07-05-Cappuccino-and-Web-applications/index.html b/Scratch/fr/blog/2010-07-05-Cappuccino-and-Web-applications/index.html new file mode 100644 index 0000000..7793a50 --- /dev/null +++ b/Scratch/fr/blog/2010-07-05-Cappuccino-and-Web-applications/index.html @@ -0,0 +1,213 @@ + + + + + + YBlog - Cappuccino ou jQuery ? + + + + + + + + + + + + + +
    + + +
    +

    Cappuccino ou jQuery ?

    +
    +
    +
    +
    +
    + +

    tlàl:

    +
      +
    • J’ai essayé de faire une version de YPassword en jQuery et avec Cappuccino.
    • +
    • Cappuccino est très bien sur les navigateurs non mobile mais l’application pèse 1.4Mo et n’est pas compatible avec l’iPhone.
    • +
    • la version jQuery n’est pas aussi jolie que la version réalisée avec Cappuccino mais elle pèse seulement 106Ko et est compatible avec l’iPhone.
    • +
    • J’essayerai Dashcode 3
    • +
    +
    + +
    +
    + +

    Avant de commencer, je dois dire que je sais que Cappuccino et jQuery ne sont pas plus comparable que Cocoa et la standard library en C++. L’un est fait pour créer des interfaces utilisateurs tandis que l’autre est plus une librairie qui aide aux tâches de bas niveaux. Par contre je les ai utilisé tous les deux pour faire la même application. C’est pourquoi je compare l’expérience que j’ai retenu de chacun pour cette tâche.

    +
    + +

    J’ai fait une version web de mon widget YPassword. C’est un simple widget qui permet d’organiser ses mots de passes simplement avec une grande sécurité et de façon portable. Ce n’est pas un widget créé pour remplacer le trousseau d’accès, mais plus un générateur de mots de passe.

    +

    Le premier a été élaboré à partir du code de mon widget Mac. Vous pouvez l’essayer ici. J’ai ensuite fait une version avec Cappuccino, que vous pouvez essayer ici.

    +

    Que fait ce widget ?

    +
    + +

    Si vous vous moquez de savoir ce que fait mon widget, vous pouvez allez directement à la section suivante.

    +
    + +

    J’organise mes mots de passe avec une méthode simple. Je mémorise un mot de passe maître. Et mon mot de passe est alors (principalement) : hash(motDePasseMaitre+NomDeDomaine)

    +

    En réalité j’ai besoin d’un plus d’informations pour créer mon mot de passe :

    +
      +
    • Un mot de passe maître ;
    • +
    • une URL ;
    • +
    • une longeur maximale de mot de passe ;
    • +
    • le type de sortie (hexadécimale ou base64) ;
    • +
    • Combien de fois mon mot de passe a dû être changé.
    • +
    +

    Le mot de passe résultant est calculé comme suit :

    +
    domainName=domaine_Name_Of_URL(url)
    +hash=sha1( masterPassword + leakedTimes + domainName )
    +if ( kind == 'base64' )
    +    hash=base64(hash)
    +end
    +return hash[0..maxlength]
    +

    En fait, selon le site web, on peut avoir des contraintes très différentes :

    +
      +
    • longueur minimale ;
    • +
    • longueur maximale ;
    • +
    • ne doit pas contenir de caractères spéciaux ;
    • +
    • doit contenir des caractères spéciaux ;
    • +
    • etc…
    • +
    +

    Et si vous souhaitez changer votre mot de passe, le nombre de changement sert à ça. Toutes les informations peuvent rester publiques sans trop de danger à l’exception du mot de passe principal.

    +

    Si vous souhaitez avoir encore plus de détails vous pouvez toujours lire certaines de mes anciens articles de blog (en anglais) :

    + +

    Cappuccino

    +

    Tout d’abord je voudrais dire que les applications réalisées avec Cappuccino sont tout simplement incroyables. C’est comme avoir une application Mac dans son navigateur.

    +

    Je dois aussi admettre que j’ai pris du plaisir a écrire mon application avec Cappuccino. C’est comme programmer une application Mac ou iPhone. Si vous connaissez bien Cocoa, vous vous sentirez comme à la maison. Si vous ne connaissez pas Cocoa, je vous conseille de vous y intéresser. Il s’agit vraiment d’un framework excellent pour faire des interfaces utilisateur. Je ne suis pas un spécialiste de tous les frameworks. Mais j’ai réalisé des Interfaces Utilisateurs avec les MFC, Java Swing1 et WXWindows il y a quelques années. Et je dois dire que Cocoa est bien meilleurs que tous ces framework.

    +

    Cappuccino est un framework spécialisé dans le développement d’application web vraiment exceptionnel. Mais il a aussi quelques défauts qui ont surgit lors de l’écriture de mon widget.

    +

    Les choses qui m’ont plu :

    +
      +
    • Le résultat est vraiment très beau
    • +
    • C’était très agréable de programmer
    • +
    • Comme programmer une application Mac
    • +
    • J’aurai pu utiliser Interface Builder pour créer l’interface.
    • +
    +

    Les choses qui ne m’ont pas plu :

    +
      +
    • J’ai mis un bon moment avant de comprendre comment récupérer le onChange des champs textuels.
    • +
    • La documentation manquait d’organisation.
    • +
    • Ça ne marche pas sous iPhone.
    • +
    • Il a fallu déployer 11Mo.
    • +
    • Il faut télécharger 1,3Mo pour que l’application se charge dans le navigateur.
    • +
    +

    Je n’ai pas utilisé les bindings parce qu’il me semble qu’ils ne sont pas prêts.

    +

    jQuery

    +

    La version jQuery de YPassword n’est pas aussi bien finie que celle de Cappuccino. Simplement parce qu’il n’y a pas de slider directement avec jQuery. Il faudrait que j’utilise jQueryUI. Et je pense que l’application deviendrait beaucoup plus lourde pour le coups. En tout cas largement au dessus des 106Ko actuels.

    +

    J’ai utilisé le code de mon widget mac en l’adaptant un peu pour faire cette version. C’était relativement facile. Mais jQuery n’est pas un framework orienté application. Il s’agit plus d’un framework pour faire des animations qui la pète.

    +

    Je n’ai pas beaucoup plus à dire sur la version jQuery, sinon que programmer avec jQuery était de la programmation de niveau beaucoup plus bas qu’avec Cappuccino.

    +

    En conclusion

    +

    Si vous voulez faire une application compatible iPhone n’utilisez pas Cappuccino. Du moins pas encore. Si vous souhaitez faire un application très simple (comme la mienne), je pense que Cappuccino est un peu trop lourd pour ça.

    +

    Si vous souhaitez faire des applications web complexes qui ressemblent à des applications de bureau alors clairement Cappuccino est un très bon choix. Notez cependant qu’il peut être un peu difficile de débuter.

    +

    Finallement, pour terminer la version web de mon widget, j’essayerai Dashcode 3. Il semblerai que ce soit une bonne alternative pour créer des widget sur le web compatible iPhone. Je ne sais pas si les applications réalisées avec Dashcode 3 sont compatibles pour les browser n’utilisant pas webkit. Mais si c’est le cas, alors ça pourrait sonner le glas des projets comme Cappuccino et Sproutcore.

    +
    +
    +
      +
    1. Si ça vous intéresse vous pouvez jeter un coup d’œil à SEDiL. Je suis assez fier de la vue automatique des arbres que j’ai programmé sans librairie de départ.

    2. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-07-05 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-07-07-CSS-rendering-problems-by-navigator/index.html b/Scratch/fr/blog/2010-07-07-CSS-rendering-problems-by-navigator/index.html new file mode 100644 index 0000000..4cd8c9d --- /dev/null +++ b/Scratch/fr/blog/2010-07-07-CSS-rendering-problems-by-navigator/index.html @@ -0,0 +1,138 @@ + + + + + + YBlog - N'utilisez pas de gradients avec Chrome + + + + + + + + + + + + + +
    + + +
    +

    N'utilisez pas de gradients avec Chrome

    +
    +
    +
    +
    +

    Beaucoup d’utilisateurs de Reddit m’ont rapporté que mon site était très long à charger et à scroller. Ils pensaient qu’il s’agissait d’un problème dû aux ombres que j’applique sur le texte. J’étais un peu surpris puisque je fais mes tests sur une machine vraiment très lente et je n’avais jamais détecté ces problèmes. En réalité, ce qui ralenti le rendu de ce site est par ordre d’importance :

    +
      +
    1. Les dégradés sur Chrome (pas dans Safari sur Mac)
    2. +
    3. les box shadows sur Firefox
    4. +
    +

    les dégradés

    +

    Sur Safari il n’y a absolument aucun problème avec les dégradés. Par contre sur Chrome sous Linux le site devient quasiment inutilisable.

    +

    Safari et Chrome utilisent webkit tous les deux. Lorsque vous accéder à ce blog avec javascript activé, un CSS spécifique à votre navigateur est ajouté. Jusqu’à maintenant je faisais un tri entre : IE, Mozilla et Webkit. Maintenant j’ai rajouté un cas particulier pour Chrome. Maintenant j’ai supprimé les gradients lorsque vous naviguer sur ce site en utilisant Chrome.

    +

    Je n’ai pas vérifier la vitesse de rendu de toutes les propriétés de CSS 3. Mais je vous conseille de ne pas utiliser -webkit-gradient avec Chrome. Au moins sous Linux.

    +

    Les ombres (box-shadow)

    +

    J’ai aussi remarqué que -moz-box-shadow ralenti le rendu sous Firefox (et sous Linux). Alors que l’équivalent webkit ne pose aucun problème à Safari sous Mac.

    +

    Ombres de texte

    +

    Beaucoup d’utilisateurs mon dit d’utiliser text-shadows avec parcimonie. Mais je pense qu’il ne s’agissait pas là du problème du ralentissement du site. C’est pourquoi je vais les remettre.

    +

    en conclusion

    +

    N’utilisez pas -webkit-gradient avec google Chrome pour l’instant. Utilisez -moz-box-shadow avec parcimonie.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-07-07 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-07-09-Indecidabilities/index.html b/Scratch/fr/blog/2010-07-09-Indecidabilities/index.html new file mode 100644 index 0000000..14d338c --- /dev/null +++ b/Scratch/fr/blog/2010-07-09-Indecidabilities/index.html @@ -0,0 +1,214 @@ + + + + + + YBlog - Indécidabilités (partie 1) + + + + + + + + + + + + + +
    + + +
    +

    Indécidabilités (partie 1)

    +
    +
    +
    +
    +

    <% # toremove_ %>

    +
    + +

    tlpl: Je crée un mode mathématique simple pour parler de différents types d’indécidabilités :

    +
      +
    • indécidabilité due aux erreurs d’observation ;
    • +
    • grandes erreurs résultant de petites erreurs de mesure ;
    • +
    • indécidabilité fractales ;
    • +
    • indécidabilité logique.
    • +
    +
    + +
    +

    Les indécidabilités

    +
    + +

    Si le monde a été fabriqué par un démiurge, on peut dire que celui-ci devait avoir le sens de l’humour. Et le récit que je vais faire va vous en fournir la preuve. Je vais me mettre à sa place. Je vais créer un monde simplifié. Un monde régi exactement par nos règles mathématiques. Puis je vais vous parler du mal qui touche cet Univers semblable au notre ; l’indécidabilité. L’incapacité de savoir si nous avons trouvé la vérité, ou seulement une approximation de celle-ci. L’incapacité de prédire certaines choses qui semblent pourtant aller de soi. Voilà comment tout aurait pu commencer.

    +
    + +

    +

    Au début, il n’y avait rien. Puis un article de blog commença à prendre forme. J’inspire profondément pour sentir la pesanteur de ce que je vais accomplir. Attention, une dernier moment de tension et je crée l’Univers. Un Univers qui n’existera que le temps de la lecture de cet article. Me voici le démiurge de cet Univers et te voilà son observateur privilégié.

    +

    Comme j’aime bien tout contrôler, je fabrique ce monde avec quelques règles simples. Je décide que les vrais règles de ce monde sont celles que nous pensons qui régissent notre monde. Notez qu’il y a une grande différence. Pour leur monde, ce que l’on croit vrai aujourd’hui, est vraiment vrai pour eux. Leur monde est donc plus simple à priori que le notre. En particulier, on peut le décrire avec des axiomes et des règles mathématiques. Alors qu’il est possible que ce ne soit pas le cas de notre Univers. Mais nous reviendront là-dessus plus tard.

    +

    Bon au travail maintenant, je crée une Terre. J’y ajoute des habitants intelligents, les Ys. Bien entendu ils se posent des questions. En particulier, ils se demandent quelles sont les lois qui régissent leur monde. Ils pensent que connaître toutes ces règles leur permettrait de connaître l’avenir. Leur naïveté est touchante. Ah, si seulement ils savaient. Mais je suis là pour les aider à apprendre.

    +

    Comme je suis un Dieu un peu facétieux, je vais leur jouer quelques tours. Sinon on s’ennuierai à mourir. Le premier est de leur donner des sens imparfaits. De plus il leur est impossible d’avoir des mesures parfaites. Je leur laisse cependant toutes libertés pour améliorer leur technologie et diminuer ces erreurs de mesures.

    +

    Les habitants de ce monde pensent que celui-ci est plat. Certains d’entre eux pensent qu’il est possible de découvrir les règles du monde que j’ai créé. Et bien que le jeu commence.

    +

    Commençons par leur première leçon, les erreurs causent de l’indécidabilité.

    +

    Indécidabilité dues aux erreurs de mesures

    +

    Voici ce que pense l’un de ces individus.

    +
    +

    Tous les triangles que j’observe semble avoir une propriété commune. La somme de leurs angles est toujours π radiants (180°). Il s’agit certainement d’une loi de mon Univers. Mais comment être certain que tous les triangles de mon Univers possèdent cette propriété ?

    +
    +

    three triangles

    +

    Certain d’entre eux commencent à formaliser un petit peu le problème et ils finissent faire une preuve mathématique. Magnifique ! La preuve est correcte, mais il reste un petit problème. La preuve s’appuie sur des axiomes et des règles. Comment être certain que ces règles et ces axiomes sont vrai dans leur monde? Ils auront beau faire des mesures de plus en plus précises qui conforteront cette formule, ils n’auront que l’espoir et jamais la certitude que la formule est vrai. Simplement parce que le seul moyen de vérifier la véracité des axiomes est par l’observation. Hors en tant que dieu facétieux, j’ai interdit les observation avec des mesures parfaites.

    +

    Bien entendu, ils prient, ils m’appellent à l’aide. Et comme tout Dieu qui se respecte, je ne réponds pas. Ah ah ah ! J’ai toujours aimé faire ce genre de chose. Ensuite je ferai comme si je n’existe pas. Encore un bonne blague !

    +

    Si certains se sentent accablés, il leur reste un espoir :

    +
    +

    Espoir

    +

    Si nous faisons de faibles erreurs de mesure, nous aurons de faibles erreurs dans nos prédictions.

    +
    +

    Indécidabilité avec erreurs croissantes

    +

    Three bodies

    +

    Malheureusement pour eux, il y a le problème des 3 corps. Prenons les formules de la gravitation Universelle et appliquons la à deux corps célestes. Si on connait la position de ces corps avec un grande précision, on pourra aussi connaître la position future de ces corps avec une grande précision. L’hypothèse selon laquelle de petite erreurs de mesures impliquent de petites erreurs prédictive est confortée. Cependant, il y a un problème. Reprenons le même problème mais avec trois corps. Par exemple, avec le Soleil, la Terre et la Lune. Dans ce cas, les erreurs de mesures initiales vont s’amplifier. S’amplifier au point de rendre toute prédiction inutilisable.

    +

    Là encore une voix d’espoir s’élève :

    +
    +

    Peut-être pouvons nous calculer l’erreur maximale acceptable pour prédire quelque chose. Et nous pourrions au moins savoir ce que nous pouvons prédire ou pas.

    +
    +

    Une fois encore, ça ne va pas très bien se passer.

    +

    Indécidabilité fractale

    +

    Considérons la question suivante :

    +

    Mandelbrot set

    +

    Soit des coordonnées GPS précises à 1m près. Les coordonnées sont proches des côtes de la Bretagne. Ce point va-t-il tomber dans la mer ou sur la terre ferme ?

    +

    Et bien, pour certaines coordonnées, c’est impossible de le savoir. Même si je réduis l’erreur à une valeur infinitésimale. Simplement parce que certains voisinages autour d’un point contiennent toujours à la fois de l’eau et de la terre. Et ce quelque soit la taille du voisinage.

    +

    On peut même imaginer une structure ou tous les points sont au bord de celle-ci, on ne peut donc pas se permettre d’erreur1.

    +

    Mais que vois-je ? Un petit malin essaye de trouver la vérité en s’extrayant de mon Monde et en faisant un article sur un blog ? Ça ne va pas se passer comme ça ! Croyez moi ! > Faire des prédictions précises à partir des données observées semble être une quête vouée à l’échec. > Mais je suis persuadé que l’on peut aller au delà. > Au diable ce Dieu qui nous empêche d’avoir des mesures précises ! > Inventons notre propre Univers mathématique. > Un monde qui se suffit à lui-même. > Un monde dans lequel il n’y aura plus d’erreur de mesure. > Un monde entièrement contrôlé par des règles que nous aurons choisi. > Un monde similaire au notre mais où tout pourra être prédit.

    +

    Indécidabilité logique

    +

    recursive stack overflow

    +

    Jusqu’ici, tous les problèmes d’indécidabilités étaient dûs aux erreurs. Maintenant peut-être que privé d’erreur de mesure, on pourrait enfin résoudre tous les problèmes.
    Et bien non. Même dans un monde mathématique complètement contrôlé. On peut créer un objet pour lequel on ne pourra pas décider à l’avance ce qu’il fait.

    +

    Il s’agit du problème de l’arrêt.

    +

    Le Théorème stipule qu’il n’existe pas de programme permettant de décider si un autre programme s’arrête. La preuve est suffisamment simple pour rentrer dans ce post, donc je me fais un petit plaisir en la donnant.

    +
    +

    Supposons qu’il existe un programme qui puisse dire si un autre programme s’arrête. Plus précisément :

    +

    Hypothèse: Il existe un programme P tel que:

    +
      +
    • P(x,y) réponde “s’arrête” en un temps fini si et seulement si x(y)2 s’arrête effectivement en temps fini et
    • +
    • P(x,y) réponde “ne s’arrête pas” en un temps fini dans le cas contraire.
    • +
    +

    Remarque: Tout code de programme est une chaîne de caractère qui peut être utilisée aussi comme entrée d’un autre programme. Ainsi écrire P(x,x) est autorisé.

    +

    Soit le programme Q que j’écris comme suit :

    +
    +Q(x) :
    +    si P(x,x)="s'arrête" alors je fais une boucle infinie.
    +    si P(x,x)="ne s'arrête pas" alors je m'arrête.
    +
    + +

    Maintenant que répond P(Q,Q)?

    +
      +
    • si P(Q,Q) répond “s’arrête” ça implique que P(Q,Q)=“ne s’arrête pas”
    • +
    • si P(Q,Q) répond “ne s’arrête pas” ça implique que P(Q,Q)=“s’arrête”
    • +
    +

    Il y a donc une contradiction que le seul moyen de régler est par la non existence du programme P.

    +
    +

    C’est simple, je suis le démiurge de ce monde imaginaire. Et même moi, je dois me soumettre à cette règle. Comme quoi, avoir la possibilité de créer le monde et la toute puissance sont deux choses différentes.

    +
    +

    Après tout ceci, il peut sembler difficile de savoir en quoi nous pouvons croire. Mais ce serait une erreur de jeter le bébé avec l’eau du bain. Dans une seconde partie, j’expliquerai ce que nous pouvons espérer et qu’elle attitude nous devons adopter une fois que l’on a réalisé que beaucoup de vérité nous sont inaccessibles.

    +
    +
    +
      +
    1. Pensez aux deux ensembles Ret Q.

    2. +
    3. C’est-à-dire le programme x prenant l’entrée y.

    4. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-08-11 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-07-31-New-style-after-holidays/index.html b/Scratch/fr/blog/2010-07-31-New-style-after-holidays/index.html new file mode 100644 index 0000000..7090d63 --- /dev/null +++ b/Scratch/fr/blog/2010-07-31-New-style-after-holidays/index.html @@ -0,0 +1,125 @@ + + + + + + YBlog - Nouveau style après les vacances + + + + + + + + + + + + + +
    + + +
    +

    Nouveau style après les vacances

    +
    +
    +
    +
    +

    Avant les vacances beaucoup d’utilisateurs se sont plaints de la lenteur de rendu de mon site. Il s’agit notamment de problèmes avec Chrome en particulier. Mais pour éviter tout problème. J’ai complètement modifié le style de mon site web. Il est inspiré du style de l’application iBooks(c) sur iPhone(c).

    +

    Dites moi ce que vous pensez de ce nouveau design.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-07-31 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-08-23-Now-heberged-on-heroku/index.html b/Scratch/fr/blog/2010-08-23-Now-heberged-on-heroku/index.html new file mode 100644 index 0000000..3425ad6 --- /dev/null +++ b/Scratch/fr/blog/2010-08-23-Now-heberged-on-heroku/index.html @@ -0,0 +1,185 @@ + + + + + + YBlog - Maintenant sur Heroku + + + + + + + + + + + + + +
    + + +
    +

    Maintenant sur Heroku

    +
    +
    +
    +
    +

    Maintenant sur Heroku

    +

    J’ai changé mon hébergeur. Mobileme n’est absolument pas adapté à la diffusion de mon blog. C’est pourquoi je suis passé à Heroku.

    +

    Mais comme vous devez le savoir mon blog est un site complètement statique. J’utilise nanoc pour l’engendrer. Avoir un site statique amène beaucoup d’avantages par rapport à un site dynamique. Surtout en terme de sécurité. Voici comment configurer un site statique sur heroku.

    +

    La racine de mes fichiers est ‘/output’. Vous devez simplement créer deux fichiers. Un fichier config.ru1 :

    +
    require 'rubygems'
    +require 'rack'
    +require 'rack/contrib'
    +require 'rack-rewrite'
    +require 'mime/types'
    +
    +use Rack::ETag
    +module ::Rack
    +    class TryStatic < Static
    +
    +        def initialize(app, options)
    +            super
    +            @try = ([''] + Array(options.delete(:try)) + [''])
    +        end
    +
    +        def call(env)
    +            @next = 0
    +            while @next < @try.size && 404 == (resp = super(try_next(env)))[0] 
    +                @next += 1
    +            end
    +            404 == resp[0] ? @app.call : resp
    +        end
    +
    +        private
    +        def try_next(env)
    +            env.merge('PATH_INFO' => env['PATH_INFO'] + @try[@next])
    +        end
    +
    +    end
    +end
    +
    +use Rack::TryStatic, 
    +    :root => "output",                              # static files root dir
    +    :urls => %w[/],                                 # match all requests 
    +    :try => ['.html', 'index.html', '/index.html']  # try these postfixes sequentially
    +
    +errorFile='output/Scratch/en/error/404-not_found/index.html'
    +run lambda { [404, {
    +                "Last-Modified"  => File.mtime(errorFile).httpdate,
    +                "Content-Type"   => "text/html",
    +                "Content-Length" => File.size(errorFile).to_s
    +            }, File.read(errorFile)] }
    +

    et un fichier .gems qui liste les gems nécessaires.

    +
    rack
    +rack-rewrite
    +rack-contrib
    +

    Maintenant il suffit de suivre l’introduction rapide d’heroku pour créer une nouvelle application :

    +
    git init
    +git add .
    +heroku create
    +git push heroku master
    +

    Maintenant je peux rediriger correctement mes erreurs 404. J’espère que ça a pu vous être utile.

    +
    +
    +
      +
    1. Je me suis complètement inspiré de cet article.

    2. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-08-23 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-08-31-send-mail-from-command-line-with-attached-file/index.html b/Scratch/fr/blog/2010-08-31-send-mail-from-command-line-with-attached-file/index.html new file mode 100644 index 0000000..a3b4f19 --- /dev/null +++ b/Scratch/fr/blog/2010-08-31-send-mail-from-command-line-with-attached-file/index.html @@ -0,0 +1,165 @@ + + + + + + YBlog - Envoyer un mail en ligne de commande avec un fichier attaché + + + + + + + + + + + + + +
    + + +
    +

    Envoyer un mail en ligne de commande avec un fichier attaché

    +
    +
    +
    +
    +

    J’ai dû envoyer un mail en ligne de commande récemment. Quelle ne fût pas ma surprise lorsque je constatais que ce n’était vraiment pas évident. Je n’avais ni pine ni mutt. Seulement mail et mailx.

    +

    Ce qu’on trouve sur internet pour envoyer un mail avec fichier attaché c’est ça :

    +
    uuencode fic.jpg fic.jpg | mail -s 'Subject'
    +

    Bon, alors, bête et discipliné j’ai essayé. Et bien, ça marche presque tout le temps. Pour mon fichier ça n’a pas marché du tout. Je l’ai compressé au format .gz, .bz2 et .zip. Avec le format .bz2 le mail reçu avait bien un fichier attaché. Mais avec les formats .gz et .zip, ça ne fonctionnait pas. Au lieu d’avoir un fichier attaché j’avais un message qui contenait quelque chose comme :

    +
    +begin 664 fic.jpg
    +M(R$O=7-R+V)I;B]E;G8@>G-H"GAL
    +                   
    +                   
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-08-31 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/index.html b/Scratch/fr/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/index.html new file mode 100644 index 0000000..0a56a35 --- /dev/null +++ b/Scratch/fr/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/index.html @@ -0,0 +1,141 @@ + + + + + + YBlog - Utilisation de git pour calculer les mtimes + + + + + + + + + + + + + +
    + + +
    +

    Utilisation de git pour calculer les mtimes

    +
    +
    +
    +
    +

    Vous pouvez remarquer qu’à la fin de chaque page je donne une date de dernière modification. Précédemment cette date était calculée en utilisant la date du fichier. Mais il arrive fréquemment que je fasse un touch d’un fichier pour engendrer tout le site de nouveau. Donc la date n’est pas nécessairement la vraie de modification du contenue.

    +

    J’utilise git pour versionner mon site web. Et cet outil me permet de récupérer la dernière date de vraie modification d’un fichier. Voici comment je m’y prend avec nanoc :

    +
    def gitmtime
    +    filepath=@item.path.sub('/Scratch/','content/html/').sub(/\/$/,'')
    +    ext=%{.#{@item[:extension]}}
    +    filepath<<=ext
    +    if not FileTest.exists?(filepath)
    +        filepath.sub!(ext,%{#{@item.raw_filename}#{ext}})
    +    end
    +    str=`git log -1 --format='%ci' -- #{filepath}`
    +    if str.nil? or str.empty?
    +        return Time.now
    +    else
    +        return DateTime.parse( str )
    +    end
    +end
    +

    Bien entendu je sais que c’est très lent et absolument pas optimisé. Mais ça fonctionne comme prévu. Maintenant la date que vous voyez en bas de la page correspond exactement à la dernière date de modification de son contenu.

    +

    Mise à jour: Je tiens à remercier Eric Sunshine et Kris pour leurs conseils sur ce problème.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-09-02 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-09-02-base64-and-sha1-on-iPhone/index.html b/Scratch/fr/blog/2010-09-02-base64-and-sha1-on-iPhone/index.html new file mode 100644 index 0000000..88a5d83 --- /dev/null +++ b/Scratch/fr/blog/2010-09-02-base64-and-sha1-on-iPhone/index.html @@ -0,0 +1,173 @@ + + + + + + YBlog - base64 et sha1 sur iPhone + + + + + + + + + + + + + +
    + + +
    +

    base64 et sha1 sur iPhone

    +
    +
    +
    +
    +

    Allons directement à l’essentiel : voici deux fonctions à intégrer à votre application iPhone pour afficher l’encodage en base64 ou en hexadecimal du hash sha1 d’un string en Objective-C pour iPhone.

    +

    Pour l’usage c’est très simple, copiez le code dans la classe de votre choix. Puis :

    +
    #import <CommonCrypto/CommonDigest.h>
    +...
    +NSString *b64_hash = [self b64_sha1:@"some NSString to be sha1'ed"];
    +...
    +NSString *hex_hash = [self hex_sha1:@"some NSString to be sha1'ed"];
    +

    L’algorithme pour l’encodage en base64 doit être programmé sur iPhone. Il n’y a pas de librairie officielle qui s’occupe de ça.

    +
    
    +- (unsigned char *)sha1:(NSString *)baseString result:(unsigned char *)result {
    +    char *c_baseString=(char *)[baseString UTF8String];
    +    CC_SHA1(c_baseString, strlen(c_baseString), result);
    +    return result;
    +}
    +
    +- (NSString *)base64:(unsigned char *)result {
    +    NSString *password=[[NSString alloc] init];
    +    static const unsigned char cb64[65]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    +    for (int i=0; i<CC_SHA1_DIGEST_LENGTH; i+=3) {
    +        password=[password stringByAppendingFormat:@"%c%c%c%c",
    +            cb64[(result[i] &0xFC)>>2],
    +            cb64[((result[i] & 0x03) << 4)
    +                | ((result[i + 1] & 0xF0) >> 4)],
    +            cb64[((result[i + 1] & 0x0F) << 2)
    +                | ((result[i + 2] & 0xC0) >> 6)],
    +            cb64[result[i+2]&0x3F]
    +                ];            
    +    }
    +    return password;
    +}
    +
    +- (NSString *)hexadecimalRepresentation:(unsigned char *)result {
    +    NSString *password=[[NSString alloc] init];
    +    for (int i=0; i<CC_SHA1_DIGEST_LENGTH; i++) {
    +        password=[password stringByAppendingFormat:@"%02x", result[i]];
    +    }
    +    return password;
    +}
    +
    +- (NSString *)b64_sha1:(NSString *)inputString {
    +    unsigned char result[CC_SHA1_DIGEST_LENGTH+1];
    +    [self sha1:inputString result:result];
    +    return [self base64:result];
    +}
    +
    +- (NSString *)hex_sha1:(NSString *)inputString {
    +    unsigned char result[CC_SHA1_DIGEST_LENGTH+1];
    +    [self sha1:inputString result:result];
    +    return [self hexadecimalRepresentation:result];
    +}
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-09-02 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-10-06-New-Blog-Design-Constraints/index.html b/Scratch/fr/blog/2010-10-06-New-Blog-Design-Constraints/index.html new file mode 100644 index 0000000..430b47d --- /dev/null +++ b/Scratch/fr/blog/2010-10-06-New-Blog-Design-Constraints/index.html @@ -0,0 +1,130 @@ + + + + + + YBlog - Contraintes du design de ce blog + + + + + + + + + + + + + +
    + + +
    +

    Contraintes du design de ce blog

    +
    +
    +
    +
    +

    Vous avez pu constater que j’ai modifié le design de mon blog. Maintenant il doit être beaucoup plus léger qu’avant. Je n’utilise plus de CSS3 et beaucoup moins de javascript. Bien entendu, même avant, mes pages étaient parfaitement lisibles sans javascript. Mais, je me suis aperçu que les systèmes de CSS3 sont loin d’être au point. J’utilisait des gradient en CSS3, ainsi que des ombres sous le texte. Ça avait un rendu très sympa. Sauf… Ce n’était pas compatible ie6, sous Chrome le rendu était d’une lenteur incroyable. J’ai donc décidé de faire un site à minima. Je voulais qu’il soit joli et le plus simple possible pour assurer sa compatibilité. Les règles que je me suis fixées sont donc:

    +
      +
    • pas d’élément CSS qui commence par -moz ou -webkit, etc… ;
    • +
    • pas d’ombre sous le texte pour donner une impression de profondeur ;
    • +
    • nettoyer pas mal le code et enlever tout ce que je peux ;
    • +
    +

    J’espère que ce nouveau design vous plaît.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-10-06 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/index.html b/Scratch/fr/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/index.html new file mode 100644 index 0000000..9c9779a --- /dev/null +++ b/Scratch/fr/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/index.html @@ -0,0 +1,147 @@ + + + + + + YBlog - Sécurisez vos emails + + + + + + + + + + + + + +
    + + +
    +

    Sécurisez vos emails

    +
    +
    +
    +
    +

    Title image

    +
    + +

    tlpl: avec un Mac

    +
      +
    • Récupérez un certificat signé par une AC: cliquez ici pour un certificat gratuit ;
    • +
    • ouvrez le fichier ;
    • +
    • supprimer le fichier en mode sécurisé ;
    • +
    • utilisez Mail plutôt que l’interface web de gmail.
    • +
    +
    + +

    J’ai (re)découvert comment adoptez la norme S/MIME. J’ai été surpris de voir à quel point ce fut aisé. Il y a seulement quelques années c’était bien plus difficile à accomplir. Maintenant je peux signer et chiffrer mes mails.

    +

    Pourquoi est-ce important ?

    +

    Signer : cela permet de certifier avec une absolue certitude que la personne qui a écrit le mail est vous ou au moins qu’elle a utilisé votre ordinateur.

    +

    Chiffrer : parce que parfois il est nécessaire d’être certain qu’une conversation reste privée.

    +

    Comment procéder ?

    +
      +
    • Récupérez un certificat signé par une authorité de certification : cliquez ici pour récupérer un certificat gratuit ;
    • +
    • ouvrez le fichier ;
    • +
    • supprimer le fichier en mode sécurisé ;
    • +
    • utilisez Mail plutôt que l’interface web de gmail. Maintenant vous devriez voir ces icônes : icône signature
    • +
    +

    n.b. : si vous utilisez gmail et que vous ne travaillez pas toujours avec un Mac, vous devriez considérer d’utiliser le module gmail S/MIME de firefox.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-10-10 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-10-14-Fun-with-wav/index.html b/Scratch/fr/blog/2010-10-14-Fun-with-wav/index.html new file mode 100644 index 0000000..099e988 --- /dev/null +++ b/Scratch/fr/blog/2010-10-14-Fun-with-wav/index.html @@ -0,0 +1,373 @@ + + + + + + YBlog - S'amuser avec un .wav + + + + + + + + + + + + + +
    + + +
    +

    S'amuser avec un .wav

    +
    +
    +
    +
    +
    + +

    tlpl: Je me suis amusé à lire un fichier wav. Le C fut le langage le mieux adapté à ce traitement. Bien meilleur que Ruby par exemple.

    +

    edit: Je voulais que ce programme fonctionne sur une machine spécifique. En aucun cas je ne pensais publier ce code pour une utilisation autre que celle-ci.

    +
    + +

    J’ai eu besoin de calculer la somme des valeurs absolue des données d’un fichier wav. Pour des raison d’efficacité (et aussi de fun), j’ai fait le programme en C.

    +

    Celà faisait longtemps que je n’avais pas programmé en C. De mémoire il était peu aisé de manipuler des fichiers. Mais je dois concéder que j’ai été étonné de la clarté du code que j’ai obtenu.

    +

    Tout d’abord, un fichier wav se compose d’un entête qui contient pas mal de meta données. Cet entête a été optimisé pour prendre peu de place. Donc on discute de l’entête avec des nombres d’octets :

    +
      +
    • Les 4 premiers octets doivent contenir RIFF en ASCII ;
    • +
    • les 4 octects suivant correspondent à un entier codé sur 32 bits qui donne la taille du fichier moins 8 octets. etc..
    • +
    +

    Etonnamment je pense que lire ce type de fichier avec un langage de haut niveau aurait été plus pénible qu’en C. La preuve, il m’a suffit de chercher sur le net le format complet de l’entête et de l’écrire dans un struct.

    +
    struct wavfile
    +{
    +    char        id[4];          // should always contain "RIFF"
    +    int     totallength;    // total file length minus 8
    +    char        wavefmt[8];     // should be "WAVEfmt "
    +    int     format;         // 16 for PCM format
    +    short     pcm;            // 1 for PCM format
    +    short     channels;       // channels
    +    int     frequency;      // sampling frequency
    +    int     bytes_per_second;
    +    short     bytes_by_capture;
    +    short     bits_per_sample;
    +    char        data[4];        // should always contain "data"
    +    int     bytes_in_data;
    +};
    +

    Si j’avais eu à faire ça en Ruby, je pense qu’il m’aurait fallu pour chaque bloc de l’entête écrire un bout de code de lecture du bon nombre d’octets. Alors qu’en C il m’a suffit d’écrire:

    +
    fread(&header,sizeof(header),1,wav)
    +

    Et en une seule étape ma structure de donnée a été remplie avec les valeurs souhaitées. Magique !

    +

    Ensuite, récupérer un entier à partir de deux octets n’est pas non plus une opération naturelle dans les nouveaux langages de programmation. Alors qu’en C. Pour récupérer un entier codé sur 16 bits il suffit d’écrire :

    +
    short value=0;
    +while( fread(&value,sizeof(value),1,wav) ) {
    +    // do something with value
    +}
    +

    Finallement je suis arrivé au code suivant, sachant que le format de wav était connu, avec notamment échantillonage sur 16 bits en 48000Hz :

    +
    #include <stdio.h>
    +#include <stdlib.h>
    +#include <stdint.h>
    +
    +struct wavfile
    +{
    +    char        id[4];          // should always contain "RIFF"
    +    int     totallength;    // total file length minus 8
    +    char        wavefmt[8];     // should be "WAVEfmt "
    +    int     format;         // 16 for PCM format
    +    short     pcm;            // 1 for PCM format
    +    short     channels;       // channels
    +    int     frequency;      // sampling frequency
    +    int     bytes_per_second;
    +    short     bytes_by_capture;
    +    short     bits_per_sample;
    +    char        data[4];        // should always contain "data"
    +    int     bytes_in_data;
    +};
    +
    +int main(int argc, char *argv[]) {
    +    char *filename=argv[1];
    +    FILE *wav = fopen(filename,"rb");
    +    struct wavfile header;
    +
    +    if ( wav == NULL ) {
    +        fprintf(stderr,"Can't open input file %s", filename);
    +        exit(1);
    +    }
    +
    +    // read header
    +    if ( fread(&header,sizeof(header),1,wav) < 1 )
    +    {
    +        fprintf(stderr,"Can't read file header\n");
    +        exit(1);
    +    }
    +    if (    header.id[0] != 'R'
    +         || header.id[1] != 'I' 
    +         || header.id[2] != 'F' 
    +         || header.id[3] != 'F' ) { 
    +        fprintf(stderr,"ERROR: Not wav format\n"); 
    +        exit(1); 
    +    }
    +
    +    fprintf(stderr,"wav format\n");
    +
    +    // read data
    +    long sum=0;
    +    short value=0;
    +    while( fread(&value,sizeof(value),1,wav) ) {
    +        // fprintf(stderr,"%d\n", value);
    +        if (value<0) { value=-value; }
    +        sum += value;
    +    }
    +    printf("%ld\n",sum);
    +    exit(0);
    +}
    +

    Bien entendu ce code n’est qu’un hack. Mais on voit bien comment on peut facilement améliorer ce code, ajouter des cas possibles par exemple. Comme je dis souvent : le bon outil pour la bonne tâche. On voit en effet que pour cette tâche C est bien supérieur à Ruby par exemple.

    +

    _màj : pour des raisons de compatibilité (machines 64 bits) j’ai utilisé int16_t au lieu de short et int au lieu de int.

    +

    Je serai curieux de savoir s’il existe un manière plus propre en Ruby que je ne connais pas. Certainement qu’en Python ça doit être la cas.

    +
    + +

    Màj (2) : après toutes les remarques concernant la portabilité. J’ai fait une nouvelle version qui devrait être plus portable. Elle fait aussi plus de test pour vérifier le fichier. Cependant j’utilise une assertion spécifique à gcc pour être certain que la structure de donnée n’ai pas de “trou” :

    +
    __attribute__((__packed__))
    +

    Le nouveau code n’utilise pas mmap et devrait être plus compatible.
    Voici le dernier résultat :

    +
    + +
    #include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h> // for memcmp
    +#include <stdint.h> // for int16_t and int32_t
    +
    +struct wavfile
    +{
    +    char    id[4];          // should always contain "RIFF"
    +    int32_t totallength;    // total file length minus 8
    +    char    wavefmt[8];     // should be "WAVEfmt "
    +    int32_t format;         // 16 for PCM format
    +    int16_t pcm;            // 1 for PCM format
    +    int16_t channels;       // channels
    +    int32_t frequency;      // sampling frequency
    +    int32_t bytes_per_second;
    +    int16_t bytes_by_capture;
    +    int16_t bits_per_sample;
    +    char    data[4];        // should always contain "data"
    +    int32_t bytes_in_data;
    +} __attribute__((__packed__));
    +
    +int is_big_endian(void) {
    +    union {
    +        uint32_t i;
    +        char c[4];
    +    } bint = {0x01000000};
    +    return bint.c[0]==1;
    +}
    +
    +int main(int argc, char *argv[]) {
    +    char *filename=argv[1];
    +    FILE *wav = fopen(filename,"rb");
    +    struct wavfile header;
    +
    +    if ( wav == NULL ) {
    +        fprintf(stderr,"Can't open input file %s\n", filename);
    +        exit(1);
    +    }
    +
    +    // read header
    +    if ( fread(&header,sizeof(header),1,wav) < 1 ) {
    +        fprintf(stderr,"Can't read input file header %s\n", filename);
    +        exit(1);
    +    }
    +
    +    // if wav file isn't the same endianness than the current environment
    +    // we quit
    +    if ( is_big_endian() ) {
    +        if (   memcmp( header.id,"RIFX", 4) != 0 ) {
    +            fprintf(stderr,"ERROR: %s is not a big endian wav file\n", filename); 
    +            exit(1);
    +        }
    +    } else {
    +        if (   memcmp( header.id,"RIFF", 4) != 0 ) {
    +            fprintf(stderr,"ERROR: %s is not a little endian wav file\n", filename); 
    +            exit(1);
    +        }
    +    }
    +
    +    if (   memcmp( header.wavefmt, "WAVEfmt ", 8) != 0 
    +        || memcmp( header.data, "data", 4) != 0 
    +            ) {
    +        fprintf(stderr,"ERROR: Not wav format\n"); 
    +        exit(1); 
    +    }
    +    if (header.format != 16) {
    +        fprintf(stderr,"\nERROR: not 16 bit wav format.");
    +        exit(1);
    +    }
    +    fprintf(stderr,"format: %d bits", header.format);
    +    if (header.format == 16) {
    +        fprintf(stderr,", PCM");
    +    } else {
    +        fprintf(stderr,", not PCM (%d)", header.format);
    +    }
    +    if (header.pcm == 1) {
    +        fprintf(stderr, " uncompressed" );
    +    } else {
    +        fprintf(stderr, " compressed" );
    +    }
    +    fprintf(stderr,", channel %d", header.pcm);
    +    fprintf(stderr,", freq %d", header.frequency );
    +    fprintf(stderr,", %d bytes per sec", header.bytes_per_second );
    +    fprintf(stderr,", %d bytes by capture", header.bytes_by_capture );
    +    fprintf(stderr,", %d bits per sample", header.bytes_by_capture );
    +    fprintf(stderr,"\n" );
    +
    +    if ( memcmp( header.data, "data", 4) != 0 ) { 
    +        fprintf(stderr,"ERROR: Prrroblem?\n"); 
    +        exit(1); 
    +    }
    +    fprintf(stderr,"wav format\n");
    +
    +    // read data
    +    long long sum=0;
    +    int16_t value;
    +    int i=0;
    +    fprintf(stderr,"---\n", value);
    +    while( fread(&value,sizeof(value),1,wav) ) {
    +        if (value<0) { value=-value; }
    +        sum += value;
    +    }
    +    printf("%lld\n",sum);
    +    exit(0);
    +}
    +

    Màj(3) : Sur reddit Bogdanp a proposé une version en Python :

    +
    #!/usr/bin/env python
    +from struct import calcsize, unpack
    +from sys import argv, exit
    +
    +def word_iter(f):
    +    while True:
    +        _bytes = f.read(2)
    +
    +    if len(_bytes) != 2:
    +        raise StopIteration
    +
    +    yield unpack("=h", _bytes)[0]
    +
    +try:
    +    with open(argv[1], "rb") as f:
    +        wav = "=4ci8cihhiihh4ci"
    +        wav_size = calcsize(wav)
    +        metadata = unpack(wav, f.read(wav_size))
    +
    +        if "".join(metadata[:4]) != "RIFF":
    +            print "error: not wav file."
    +            exit(1)
    +
    +        print sum(abs(word) for word in word_iter(f))
    +except IOError:
    +    print "error: can't open input file '%s'." % argv[1]
    +    exit(1)
    +

    et luikore a proposé une version Ruby assez impressionnante :

    +
    data = ARGF.read
    + keys = %w[id totallength wavefmt format
    +       pcm channels frequency bytes_per_second
    +         bytes_by_capture bits_per_sample
    +           data bytes_in_data sum
    + ]
    + values = data.unpack 'Z4 i Z8 i s s i i s s Z4 i s*'
    + sum = values.drop(12).map(&:abs).inject(:+)
    + keys.zip(values.take(12) << sum) {|k, v|
    +       puts "#{k.ljust 17}: #{v}"
    + }
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-10-14 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2010-10-26-LaTeX-like-macro-and-markdown/index.html b/Scratch/fr/blog/2010-10-26-LaTeX-like-macro-and-markdown/index.html new file mode 100644 index 0000000..75edb3c --- /dev/null +++ b/Scratch/fr/blog/2010-10-26-LaTeX-like-macro-and-markdown/index.html @@ -0,0 +1,181 @@ + + + + + + YBlog - Des macros LaTeX pour markdown + + + + + + + + + + + + + +
    + + +
    +

    Des macros LaTeX pour markdown

    +
    +
    +
    +
    +
    + +

    tlpl: J’ai fait un système simple de macros pour mon blog. Par exemple, il me suffit d’écrire %latex et ça affiche LaTeX.

    +
    + +

    J’ai ajouter un système de macro pour mon système de blog. Lorsqu’on est habitué à LaTeX et que l’on commence à écrire des articles un peu conséquent avec des notations mathématiques, les macros deviennent vite quelque chose d’indispensable.

    +

    Dans l’entête de mes fichiers j’écris simplement:

    +
    +

    Puis dans le corps ça va remplacer :

    +
      +
    • %test par Just a test ;
    • +
    • et %latex par LaTeX
    • +
    +

    Le code est assez simple. Pour les utilisateurs de nanoc il suffit de copier le fichier suivant dans le répertoire lib.

    +
    # usage:
    +# ---
    +# ...
    +# macros:
    +#   test: "passed test"
    +# ---
    +# ...
    +# Here is a Just a test.
    +#
    +class Macros < Nanoc3::Filter
    +    identifier :falacy
    +    attr_accessor :macro
    +    def initialize(arg)
    +        super
    +        @macro={}
    +        @macro[:tlal] = %{<span class="sc"><abbr title="Trop long à lire">tlàl</abbr> : </span>}
    +        @macro[:tldr] = %{<span class="sc"><abbr title="Too long; didn't read">tl;dr</abbr>: </span>}
    +        if @item.nil?
    +            if not arg.nil?
    +                @macro.merge!( arg )
    +            end
    +        else
    +            if not @item[:macros].nil?
    +                @macro.merge!( @item[:macros] )
    +            end
    +        end
    +    end
    +    def macro_value_for(macro_name)
    +        if macro_name.nil? or macro_name=="" or @macro[macro_name.intern].nil?
    +            return %{%#{macro_name}} 
    +        end
    +        return @macro[macro_name.intern]
    +    end
    +    def run(content, params={})
    +        content.gsub(/%(\w*)/) do |m| 
    +            if m != '%'
    +                macro_value_for($1)
    +            else
    +                m
    +            end
    +        end
    +    end
    +end
    +

    Les macros peuvent être vraiment utiles. Lisez cet article par exemple.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2010-10-26 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2011-01-03-Happy-New-Year/index.html b/Scratch/fr/blog/2011-01-03-Happy-New-Year/index.html new file mode 100644 index 0000000..6545352 --- /dev/null +++ b/Scratch/fr/blog/2011-01-03-Happy-New-Year/index.html @@ -0,0 +1,127 @@ + + + + + + YBlog - Bonne et heureuse année + + + + + + + + + + + + +
    + + +
    +

    Bonne et heureuse année

    +
    +
    +
    +
    +

    Bonne et heureuse année !

    +

    J’étais très occupé ces derniers mois. Maintenant il me semble que je vais pouvoir faire revivre ce blog.

    +

    J’ai fait un outil qui permet d’écrire des livre en utilisant une syntaxe proche de markdown. C’est un markdown avec des macros (essentiel pour les textes longs). De plus le système gère la génération de pages HTML ainsi que du PDF engendré avec du XeLaTeX. Je n’en ai pas encore terminé avec ça. Mais si je tarde trop, je communiquerai lorsque j’aurai fini le minimum.

    +

    J’ai écrit un framework MVC pour application javascript simple mais néanmoins très rapide.

    +

    Meilleurs vœux à tous !

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-01-01 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/index.html b/Scratch/fr/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/index.html new file mode 100644 index 0000000..87cb53d --- /dev/null +++ b/Scratch/fr/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/index.html @@ -0,0 +1,200 @@ + + + + + + YBlog - Pourquoi je n'utiliserai pas CoffeeScript (malheureusement) + + + + + + + + + + + + + +
    + + +
    +

    Pourquoi je n'utiliserai pas CoffeeScript (malheureusement)

    +
    +
    +
    +
    +

    Title image

    +
    + +

    Mise à jour : Je pense que je vais finallement changer d’avis. Pourquoi ? Tout d’abord, je viens de découvrir un convertisseur javascript vers coffeescript, ensuite Denis Knauf m’a laissé un commentaire et m’a appris l’existence d’une fonction CoffeeScript.eval. De plus, il faut voir CoffeeScript comme javascript avec une syntaxe similaire à Ruby et pas comme un langage similaire à Ruby.

    +
    + +
    + +

    tlpl: Qu’est-ce qui n’allait pas avec Coffeescript? La meta-programmation, il faut le “vendre” aux autres, une nouvelle étape de compilation intermédiaire sans fournir les avantages de Cappuccino, la sensation que c’est un peu instable.

    +
    + +

    Le commentaire le mieux classé de la question suivante posée sur HackerNews mentionnait CoffeeScript. Récemment j’ai beaucoup programmé en javascript. Après avoir essayé Sroutcore, Cappuccino, backbone.js & javascriptMVC, Je me suis décidé à créer mon propre framework MVC minimal pour client javascript.1

    +

    Je me suis battu avec l’horrible syntaxe de javascript. C’était comme revenir des années dans le passé :

    +
      +
    • une syntaxe à la Java très verbeuse ;
    • +
    • une syntaxe follement verbeuse et étrange pour la programmation orientée objet ;
    • +
    • pas de manière naturelle de se référer à l’instance d’une classe ;
    • +
    • etc…
    • +
    +

    J’étais tellement ennuyé par tous ces point qu’il était arrivé un moment où je commençais à vouloir faire mon propre CoffeeScript.

    +

    J’ai fini une première version de mon framework MVC en javascript et j’ai appris l’existence de CoffeeScript. Merci à git, j’ai immédiatement créé une nouvelle branche dans le seul but d’essayer CoffeeScript.

    +

    Voici mon expérience :

    +
      +
    1. J’ai dû installer node.js et utiliser npm simplement pour utiliser CoffeeScript. Ce n’était pas très difficile, mais pas aussi facile que ce que j’aurai aimé.
    2. +
    3. Les fichier javascript existants ne sont pas compatible avec coffee.
    4. +
    5. Il n’y a pas script pour aider à transformer les anciens fichiers javascripts en fichier coffee. Du coups j’ai dû faire ça manuellement. Merci à vim, il ne fut pas très difficile de transformer 90% des fichiers avec des expressions régulières. L’option --watch de coffee était très utile pour debugger cette transformation. Cependant, il m’a fallu écrire mon propre script pour que tous mes fichiers soient watchés dans tous les sous-répertoires.
    6. +
    7. Quelque chose à laquelle je n’avais pas pensé. J’ai fait un peu de meta-programmation en javascript en utilisant eval. Mais pour que celà fonctionne correctement, il faut que la chaîne de caractère que je passe à eval soit codée en javascript et pas en coffee. C’est un peu comme écrire dans deux langages différents au même endroit. Ça ne me parraissait vraiment pas agréable.
    8. +
    +

    Conclusion

    +

    Avantages :

    +
      +
    • Code plus lisible : CoffeeScript résoud la majorité des problèmes de syntaxes de javascript
    • +
    • Concision : j’ai gagné 14% de lignes, 22% de mots et 14% de caractères.
    • +
    +

    Inconvénients :

    +
      +
    • Ajout d’une nouvelle étape de compilation avant de pouvoir vérifier le comportement de mon site
    • +
    • Facilité d’utilisation : il m’a fallu créer un script pour gérer la génératio automatique des fichiers
    • +
    • Il faut apprendre un autre langage proche de ruby
    • +
    • La meta-programmation devient une expérience désagréable
    • +
    • Je dois convaincre les personnes travaillant avec moi : +
        +
      • d’installer node.js, npm et CoffeeScript ;
      • +
      • de se souvenir de lancer un script à chaque session de codage ;
      • +
      • d’apprendre un autre language proche de ruby.
      • +
    • +
    +

    Les deux derniers points étant de mon point de vue les plus problématiques.

    +

    Mais même si j’avais à travailler seul, je n’utiliserai certainement pas CoffeeScript. Il s’agit d’un tier dont la moindre mise à jour pourrait rendre mon code inutilisable. Cette situation m’est déjà arrivée plusieurs fois et c’est très désagrable. Beaucoup plus que coder avec une mauvaise syntaxe.

    +

    Digression

    +

    Je suis attristé. J’espérais tant pouvoir programmer Javascript avec une touche de Ruby. En fin de compte, cette solution n’est pas pour moi. Je vais devoir utiliser l’horrible syntaxe javascript pour l’instant. À la limite j’aurai préféré un script Ruby2Js par exemple2. Mais il me semble que ça serait un travail très difficile rien que pour simuler l’accès à la classe courante.

    +

    Typiquement @x est transformé en this.x. Mais le code suivant ne fait pas ce que j’attendrai de lui.

    +
    -> 
    +class MyClass
    +  foo: ->
    +    alert('ok')
    +
    +  bar: ->
    +    $('#content').load( '/content.html', ( -> @foo(x) ) )
    +    # Ça n'appellera pas MyClass.foo
    +

    La seule façon de résoudre ce problème est avec le code suivant :

    +
    -> 
    +class MyClass
    +  foo: ->
    +    alert('ok')
    +
    +  bar: ->
    +    self=this
    +    $('#content').load( '/content.html', ( -> self.foo(x) ) )
    +

    Sachant celà, la notation @ perd tout son intérêt pour moi.

    +
    +
    +
      +
    1. Je sais que ce n’est certainement ni la meilleure ni la plus productive des décisions. Mais j’aime bien fabriquer les choses pour savoir comment tout fonctionne dans le détail.

    2. +
    3. Je sais qu’il existe un projet rb2js, mais il ne résoud pas le problème dont je parle.

    4. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-01-03 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/2011-04-20-Now-hosted-on-github/index.html b/Scratch/fr/blog/2011-04-20-Now-hosted-on-github/index.html new file mode 100644 index 0000000..56496e1 --- /dev/null +++ b/Scratch/fr/blog/2011-04-20-Now-hosted-on-github/index.html @@ -0,0 +1,124 @@ + + + + + + YBlog - Hébergement github + + + + + + + + + + + + +
    + + +
    +

    Hébergement github

    +
    +
    +
    +
    +

    Title image

    +

    J’héberge mon site sur github à partir d’aujourd’hui.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-04-20 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/A-more-convenient-diff/index.html b/Scratch/fr/blog/A-more-convenient-diff/index.html new file mode 100644 index 0000000..31ecbb1 --- /dev/null +++ b/Scratch/fr/blog/A-more-convenient-diff/index.html @@ -0,0 +1,148 @@ + + + + + + YBlog - Un diff plus pratique + + + + + + + + + + + + + +
    + + +
    +

    Un diff plus pratique

    +
    +
    +
    +
    +

    diff est un utilitaire très pratique, mais il n’est pas facile à lire pour nous, les Hommes.

    +

    C’est pourquoi, lorsque vous utilisez git, il vous montre un formatage plus agréable avec des couleurs.

    +

    Voici le script que j’utilise lorsque je veux avoir un diff à la git.

    +
    #!/usr/bin/env zsh
    +
    +# Load colors helpers
    +autoload -U colors && colors
    +
    +function colorize_diff {
    +    while read line; do
    +    case ${line[0]} in
    +    +) print -n $fg[green];;
    +    -) print -n $fg[red];;
    +    @) # Display in cyan the @@ positions @@
    +       if [[ ${line[1]} = '@' ]]; then
    +           line=$(print $line | perl -pe 's#(\@\@[^\@]*\@\@)(.*)$#'$fg[cyan]'$1'$reset_color'$2#')
    +       fi;;
    +
    +    esac
    +        print -- $line
    +        print -n $reset_color
    +        done
    +}
    +
    +diff -u $* | colorize_diff
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-08-17 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/Category-Theory-Presentation/index.html b/Scratch/fr/blog/Category-Theory-Presentation/index.html new file mode 100644 index 0000000..0aa462b --- /dev/null +++ b/Scratch/fr/blog/Category-Theory-Presentation/index.html @@ -0,0 +1,1090 @@ + + + + + + YBlog - Category Theory Presentation + + + + + + + + + + + + + +
    + + +
    +

    Category Theory Presentation

    +
    +
    +
    +
    + Cateogry of Hask's endofunctors + +

    Yesterday I was happy to make a presentation about Category Theory at Riviera Scala Clojure Meetup (note I used only Haskell for my examples).

    + + + +

    If you don't want to read them through an HTML presentations framework or downloading a big PDF +just continue to read as a standard web page. +

    + +
    +\(\newcommand{\F}{\mathbf{F}}\) +\(\newcommand{\E}{\mathbf{E}}\) +\(\newcommand{\C}{\mathcal{C}}\) +\(\newcommand{\D}{\mathcal{D}}\) +\(\newcommand{\id}{\mathrm{id}}\) +\(\newcommand{\ob}[1]{\mathrm{ob}(#1)}\) +\(\newcommand{\hom}[1]{\mathrm{hom}(#1)}\) +\(\newcommand{\Set}{\mathbf{Set}}\) +\(\newcommand{\Mon}{\mathbf{Mon}}\) +\(\newcommand{\Vec}{\mathbf{Vec}}\) +\(\newcommand{\Grp}{\mathbf{Grp}}\) +\(\newcommand{\Rng}{\mathbf{Rng}}\) +\(\newcommand{\ML}{\mathbf{ML}}\) +\(\newcommand{\Hask}{\mathbf{Hask}}\) +\(\newcommand{\Cat}{\mathbf{Cat}}\) +\(\newcommand{\fmap}{\mathtt{fmap}}\) +
    + +
    +

    Category Theory & Programming

    +
    for Rivieria Scala Clojure (Note this presentation uses Haskell)
    +by Yann Esposito +
    + + @yogsototh, + + + +yogsototh + +
    +
    +
    +

    Plan

    +
      +
    • General overview
    • +
    • Definitions
    • +
    • Applications
    • +
    +
    +
    +

    Not really about: Cat & glory

    +
    +Cat n glory
    credit to Tokuhiro Kawai (川井徳寛)
    +
    + +
    +
    +

    General Overview

    +
    +Samuel Eilenberg Saunders Mac Lane +
    + +

    Recent Math Field
    1942-45, Samuel Eilenberg & Saunders Mac Lane

    +

    Certainly one of the more abstract branches of math

    +
      +
    • New math foundation
      formalism abstraction, package entire theory
    • +
    • Bridge between disciplines
      Physics, Quantum Physics, Topology, Logic, Computer Science
    • +
    +

    +★: When is one thing equal to some other thing?, Barry Mazur, 2007
    ☆: Physics, Topology, Logic and Computation: A Rosetta Stone, John C. Baez, Mike Stay, 2009 +

    + +
    +
    +

    From a Programmer perspective

    +
    +

    Category Theory is a new language/framework for Math

    +
    +
      +
    • Another way of thinking
    • +
    • Extremely efficient for generalization
    • +
    +
    +
    +

    Math Programming relation

    +Buddha Fractal +

    Programming is doing Math

    +

    Strong relations between type theory and category theory.

    +

    Not convinced?
    Certainly a vocabulary problem.

    +

    One of the goal of Category Theory is to create a homogeneous vocabulary between different disciplines.

    +
    +
    +

    Vocabulary

    +mind blown +

    Math vocabulary used in this presentation:

    +
    +

    Category, Morphism, Associativity, Preorder, Functor, Endofunctor, Categorial property, Commutative diagram, Isomorph, Initial, Dual, Monoid, Natural transformation, Monad, Klesli arrows, κατα-morphism, ...

    +
    +
    +
    +

    Programmer Translation

    +lolcat + + + + + + + + +
    +Mathematician + +Programmer +
    +Morphism + +Arrow +
    +Monoid + +String-like +
    +Preorder + +Acyclic graph +
    +Isomorph + +The same +
    +Natural transformation + +rearrangement function +
    +Funny Category + +LOLCat +
    + +
    +
    +

    Plan

    +
      +
    • General overview
    • +
    • Definitions +
        +
      • Category
      • +
      • Intuition
      • +
      • Examples
      • +
      • Functor
      • +
      • Examples
      • +
      +
    • +
    • Applications
    • +
    +
    +
    +

    Category

    + +

    A way of representing things and ways to go between things.

    + +

    A Category \(\mathcal{C}\) is defined by:

    +
      +
    • Objects \(\ob{C}\),
    • +
    • Morphisms \(\hom{C}\),
    • +
    • a Composition law (∘)
    • +
    • obeying some Properties.
    • +
    +
    +
    +

    Category: Objects

    + +objects + +

    \(\ob{\mathcal{C}}\) is a collection

    +
    +
    +

    Category: Morphisms

    + +morphisms + +

    \(A\) and \(B\) objects of \(\C\)
    +\(\hom{A,B}\) is a collection of morphisms
    +\(f:A→B\) denote the fact \(f\) belongs to \(\hom{A,B}\)

    +

    \(\hom{\C}\) the collection of all morphisms of \(\C\)

    +
    +
    +

    Category: Composition

    +

    Composition (∘): associate to each couple \(f:A→B, g:B→C\) + $$g∘f:A\rightarrow C$$ +

    +composition +
    +
    +

    Category laws: neutral element

    +

    for each object \(X\), there is an \(\id_X:X→X\),
    +such that for each \(f:A→B\):

    +identity +
    +
    +

    Category laws: Associativity

    +

    Composition is associative:

    +associative composition +
    +
    +

    Commutative diagrams

    + +

    Two path with the same source and destination are equal.

    +
    + Commutative Diagram (Associativity) +
    + \((h∘g)∘f = h∘(g∘f) \) +
    +
    +
    + Commutative Diagram (Identity law) +
    + \(id_B∘f = f = f∘id_A \) +
    +
    +
    +
    +

    Question Time!

    + +
    + +
    +- French-only joke - +
    +
    +
    +
    +

    Can this be a category?

    +

    \(\ob{\C},\hom{\C}\) fixed, is there a valid ∘?

    +
    + Category example 1 +
    + YES +
    +
    +
    + Category example 2 +
    + no candidate for \(g∘f\) +
    NO +
    +
    +
    + Category example 3 +
    + YES +
    +
    +
    +
    +

    Can this be a category?

    +
    + Category example 4 +
    + no candidate for \(f:C→B\) +
    NO +
    +
    +
    + Category example 5 +
    + \((h∘g)∘f=\id_B∘f=f\)
    + \(h∘(g∘f)=h∘\id_A=h\)
    + but \(h≠f\)
    + NO +
    +
    +
    +
    +

    Categories Examples

    + +
    +Basket of cats +
    +- Basket of Cats - +
    +
    +
    +
    +

    Category \(\Set\)

    + +
      +
    • \(\ob{\Set}\) are all the sets
    • +
    • \(\hom{E,F}\) are all functions from \(E\) to \(F\)
    • +
    • ∘ is functions composition
    • +
    + +
      +
    • \(\ob{\Set}\) is a proper class ; not a set
    • +
    • \(\hom{E,F}\) is a set
    • +
    • \(\Set\) is then a locally small category
    • +
    +
    +
    +

    Categories Everywhere?

    +Cats everywhere +
      +
    • \(\Mon\): (monoids, monoid morphisms,∘)
    • +
    • \(\Vec\): (Vectorial spaces, linear functions,∘)
    • +
    • \(\Grp\): (groups, group morphisms,∘)
    • +
    • \(\Rng\): (rings, ring morphisms,∘)
    • +
    • Any deductive system T: (theorems, proofs, proof concatenation)
    • +
    • \( \Hask\): (Haskell types, functions, (.) )
    • +
    • ...
    • +
    +
    +
    +

    Smaller Examples

    + +

    Strings

    +Monoids are one object categories +
      +
    • \(\ob{Str}\) is a singleton
    • +
    • \(\hom{Str}\) each string
    • +
    • ∘ is concatenation (++)
    • +
    +
      +
    • "" ++ u = u = u ++ ""
    • +
    • (u ++ v) ++ w = u ++ (v ++ w)
    • +
    +
    +
    +

    Finite Example?

    + +

    Graph

    +
    +Each graph is a category +
    +
      +
    • \(\ob{G}\) are vertices
    • +
    • \(\hom{G}\) each path
    • +
    • ∘ is path concatenation
    • +
    +
    • \(\ob{G}=\{X,Y,Z\}\), +
    • \(\hom{G}=\{ε,α,β,γ,αβ,βγ,...\}\) +
    • \(αβ∘γ=αβγ\) +
    +
    +
    +

    Number construction

    + +

    Each Numbers as a whole category

    +Each number as a category +
    +
    +

    Degenerated Categories: Monoids

    + +Monoids are one object categories +

    Each Monoid \((M,e,⊙): \ob{M}=\{∙\},\hom{M}=M,\circ = ⊙\)

    +

    Only one object.

    +

    Examples:

    +
    • (Integer,0,+), (Integer,1,*), +
    • (Strings,"",++), for each a, ([a],[],++) +
    +
    +
    +

    Degenerated Categories: Preorders \((P,≤)\)

    + +
    • \(\ob{P}={P}\), +
    • \(\hom{x,y}=\{x≤y\} ⇔ x≤y\), +
    • \((y≤z) \circ (x≤y) = (x≤z) \) +
    + +

    At most one morphism between two objects.

    + +preorder category +
    +
    +

    Degenerated Categories: Discrete Categories

    + +Any set can be a category +

    Any Set

    +

    Any set \(E: \ob{E}=E, \hom{x,y}=\{x\} ⇔ x=y \)

    +

    Only identities

    +
    +
    +

    Choice

    +

    The same object can be seen in many different way as a category.

    +

    You can choose what are object, morphisms and composition.

    +

    ex: Str and discrete(Σ*)

    +
    +
    +

    Categorical Properties

    + +

    Any property which can be expressed in term of category, objects, morphism and composition.

    + +
    • Dual: \(\D\) is \(\C\) with reversed morphisms. +
    • Initial: \(Z\in\ob{\C}\) s.t. \(∀Y∈\ob{\C}, \#\hom{Z,Y}=1\) +
      Unique ("up to isormophism") +
    • Terminal: \(T\in\ob{\C}\) s.t. \(T\) is initial in the dual of \(\C\) +
    • Functor: structure preserving mapping between categories +
    • ... +
    +
    +
    +

    Isomorph

    +

    isomorph cats isomorphism: \(f:A→B\) which can be "undone" i.e.
    \(∃g:B→A\), \(g∘f=id_A\) & \(f∘g=id_B\)
    in this case, \(A\) & \(B\) are isomorphic.

    +

    A≌B means A and B are essentially the same.
    In Category Theory, = is in fact mostly .
    For example in commutative diagrams.

    +
    +
    +

    Functor

    + +

    A functor is a mapping between two categories. +Let \(\C\) and \(\D\) be two categories. +A functor \(\F\) from \(\C\) to \(\D\):

    +
      +
    • Associate objects: \(A\in\ob{\C}\) to \(\F(A)\in\ob{\D}\)
    • +
    • Associate morphisms: \(f:A\to B\) to \(\F(f) : \F(A) \to \F(B)\) + such that +
        +
      • \( \F (\)\(\id_X\)\()= \)\(\id\)\(\vphantom{\id}_{\F(}\)\(\vphantom{\id}_X\)\(\vphantom{\id}_{)} \),
      • +
      • \( \F (\)\(g∘f\)\()= \)\( \F(\)\(g\)\() \)\(\circ\)\( \F(\)\(f\)\() \)
      • +
      +
    • +
    +
    +
    +

    Functor Example (ob → ob)

    + +Functor +
    +
    +

    Functor Example (hom → hom)

    + +Functor +
    +
    +

    Functor Example

    + +Functor +
    +
    +

    Endofunctors

    + +

    An endofunctor for \(\C\) is a functor \(F:\C→\C\).

    +Endofunctor +
    +
    +

    Category of Categories

    + + + +

    Categories and functors form a category: \(\Cat\)

    +
    • \(\ob{\Cat}\) are categories +
    • \(\hom{\Cat}\) are functors +
    • ∘ is functor composition +
    +
    +
    +

    Plan

    +
      +
    • General overview
    • +
    • Definitions
    • +
    • Applications +
        +
      • \(\Hask\) category +
      • Functors +
      • Natural transformations +
      • Monads +
      • κατα-morphisms +
      +
    • +
    +
    +
    +

    Hask

    + +

    Category \(\Hask\):

    + +Haskell Category Representation + +
    • +\(\ob{\Hask} = \) Haskell types +
    • +\(\hom{\Hask} = \) Haskell functions +
    • +∘ = (.) Haskell function composition +
    + +

    Forget glitches because of undefined.

    +
    +
    +

    Haskell Kinds

    +

    In Haskell some types can take type variable(s). Typically: [a].

    +

    Types have kinds; The kind is to type what type is to function. Kind are the types for types (so meta).

    +
    Int, Char :: *
    +[], Maybe :: * -> *
    +(,), (->) :: * -> * -> *
    +[Int], Maybe Char, Maybe [Int] :: *
    +
    +
    +

    Haskell Types

    +

    Sometimes, the type determine a lot about the function:

    +
    fst :: (a,b) -> a -- Only one choice
    +snd :: (a,b) -> b -- Only one choice
    +f :: a -> [a]     -- Many choices
    +-- Possibilities: f x=[], or [x], or [x,x] or [x,...,x]
    +
    +? :: [a] -> [a] -- Many choices
    +-- can only rearrange: duplicate/remove/reorder elements
    +-- for example: the type of addOne isn't [a] -> [a]
    +addOne l = map (+1) l
    +-- The (+1) force 'a' to be a Num.
    + +

    +

    ★:Theorems for free!, Philip Wadler, 1989

    +
    +
    +

    Haskell Functor vs \(\Hask\) Functor

    + +

    A Haskell Functor is a type F :: * -> * which belong to the type class Functor ; thus instantiate +fmap :: (a -> b) -> (F a -> F b). + +

    & F: \(\ob{\Hask}→\ob{\Hask}\)
    & fmap: \(\hom{\Hask}→\hom{\Hask}\) + +

    The couple (F,fmap) is a \(\Hask\)'s functor if for any x :: F a:

    +
    • fmap id x = x +
    • fmap (f.g) x= (fmap f . fmap g) x +
    +
    +
    +

    Haskell Functors Example: Maybe

    + +
    data Maybe a = Just a | Nothing
    +instance Functor Maybe where
    +    fmap :: (a -> b) -> (Maybe a -> Maybe b)
    +    fmap f (Just a) = Just (f a)
    +    fmap f Nothing = Nothing
    +
    fmap (+1) (Just 1) == Just 2
    +fmap (+1) Nothing  == Nothing
    +fmap head (Just [1,2,3]) == Just 1
    +
    +
    +

    Haskell Functors Example: List

    + +
    instance Functor ([]) where
    +	fmap :: (a -> b) -> [a] -> [b]
    +	fmap = map
    +
    fmap (+1) [1,2,3]           == [2,3,4]
    +fmap (+1) []                == []
    +fmap head [[1,2,3],[4,5,6]] == [1,4]
    +
    +
    +

    Haskell Functors for the programmer

    +

    Functor is a type class used for types that can be mapped over.

    +
      +
    • Containers: [], Trees, Map, HashMap...
    • +
    • "Feature Type": +
        +
      • Maybe a: help to handle absence of a.
        Ex: safeDiv x 0 ⇒ Nothing
      • +
      • Either String a: help to handle errors
        Ex: reportDiv x 0 ⇒ Left "Division by 0!"
      • +
    • +
    +
    +
    +

    Haskell Functor intuition

    + +

    Put normal function inside a container. Ex: list, trees...

    + +Haskell Functor as a box play +

    +
    +

    Haskell Functor properties

    + +

    Haskell Functors are:

    + +
    • endofunctors ; \(F:\C→\C\) here \(\C = \Hask\), +
    • a couple (Object,Morphism) in \(\Hask\). +
    +
    +
    +

    Functor as boxes

    + +

    Haskell functor can be seen as boxes containing all Haskell types and functions. +Haskell types look like a fractal:

    + +Haskell functor representation +
    +
    +

    Functor as boxes

    + +

    Haskell functor can be seen as boxes containing all Haskell types and functions. +Haskell types look like a fractal:

    + +Haskell functor representation +
    +
    +

    Functor as boxes

    + +

    Haskell functor can be seen as boxes containing all Haskell types and functions. +Haskell types look like a fractal:

    + +Haskell functor representation +
    +
    +

    "Non Haskell" Hask's Functors

    +

    A simple basic example is the \(id_\Hask\) functor. It simply cannot be expressed as a couple (F,fmap) where

    +
      +
    • F::* -> *
    • +
    • fmap :: (a -> b) -> (F a) -> (F b)
    • +
    +

    Another example:

    +
      +
    • F(T)=Int
    • +
    • F(f)=\_->0
    • +
    +
    +
    +

    Also Functor inside \(\Hask\)

    +

    \(\mathtt{[a]}∈\ob{\Hask}\) but is also a category. Idem for Int.

    +

    length is a Functor from the category [a] to the category Int:

    +
      +
    • \(\ob{\mathtt{[a]}}=\{∙\}\)
    • +
    • \(\hom{\mathtt{[a]}}=\mathtt{[a]}\)
    • +
    • \(∘=\mathtt{(++)}\)
    • +
    +

    +
      +
    • \(\ob{\mathtt{Int}}=\{∙\}\)
    • +
    • \(\hom{\mathtt{Int}}=\mathtt{Int}\)
    • +
    • \(∘=\mathtt{(+)}\)
    • +
    +
    +
    • id: length [] = 0 +
    • comp: length (l ++ l') = (length l) + (length l') +
    +
    +
    +

    Category of \(\Hask\) Endofunctors

    +Category of Hask endofunctors +
    +
    +

    Category of Functors

    +

    If \(\C\) is small (\(\hom{\C}\) is a set). All functors from \(\C\) to some category \(\D\) form the category \(\mathrm{Func}(\C,\D)\).

    +
      +
    • \(\ob{\mathrm{Func}(\C,\D)}\): Functors \(F:\C→\D\)
    • +
    • \(\hom{\mathrm{Func}(\C,\D)}\): natural transformations
    • +
    • ∘: Functor composition
    • +
    +

    \(\mathrm{Func}(\C,\C)\) is the category of endofunctors of \(\C\).

    +
    +
    +

    Natural Transformations

    +

    Let \(F\) and \(G\) be two functors from \(\C\) to \(\D\).

    +

    Natural transformation commutative diagram A natural transformation: familly η ; \(η_X\in\hom{\D}\) for \(X\in\ob{\C}\) s.t.

    +

    ex: between Haskell functors; F a -> G a
    Rearragement functions only.

    +
    +
    +

    Natural Transformation Examples (1/4)

    +
    data List a = Nil | Cons a (List a)
    +toList :: [a] -> List a
    +toList [] = Nil
    +toList (x:xs) = Cons x (toList xs)
    +

    toList is a natural transformation. It is also a morphism from [] to List in the Category of \(\Hask\) endofunctors.

    +natural transformation commutative diagram +
    +natural transformation commutative diagram +
    + +
    +
    +

    Natural Transformation Examples (2/4)

    +
    data List a = Nil | Cons a (List a)
    +toHList :: List a -> [a]
    +toHList Nil = []
    +toHList (Cons x xs) = x:toHList xs
    +

    toHList is a natural transformation. It is also a morphism from List to [] in the Category of \(\Hask\) endofunctors.

    +natural transformation commutative diagram +
    +natural transformation commutative diagram
    toList . toHList = id & toHList . toList = id &
    therefore [] & List are isomorph.
    +
    + +
    +
    +

    Natural Transformation Examples (3/4)

    +
    toMaybe :: [a] -> Maybe a
    +toMaybe [] = Nothing
    +toMaybe (x:xs) = Just x
    +

    toMaybe is a natural transformation. It is also a morphism from [] to Maybe in the Category of \(\Hask\) endofunctors.

    +natural transformation commutative diagram +
    +natural transformation commutative diagram +
    + +
    +
    +

    Natural Transformation Examples (4/4)

    +
    mToList :: Maybe a -> [a]
    +mToList Nothing = []
    +mToList Just x  = [x]
    +

    toMaybe is a natural transformation. It is also a morphism from [] to Maybe in the Category of \(\Hask\) endofunctors.

    +natural transformation commutative diagram +
    +relation between [] and Maybe
    There is no isomorphism.
    Hint: Bool lists longer than 1.
    +
    + +
    +
    +

    Composition problem

    +

    The Problem; example with lists:

    +
    f x = [x]       ⇒ f 1 = [1]   ⇒ (f.f) 1 = [[1]] ✗
    +g x = [x+1]     ⇒ g 1 = [2]   ⇒ (g.g) 1 = ERROR [2]+1 ✗
    +h x = [x+1,x*3] ⇒ h 1 = [2,3] ⇒ (h.h) 1 = ERROR [2,3]+1 ✗ 
    + +

    The same problem with most f :: a -> F a functions and functor F.

    +
    +
    +

    Composition Fixable?

    +

    How to fix that? We want to construct an operator which is able to compose:

    +

    f :: a -> F b & g :: b -> F c.

    +

    More specifically we want to create an operator ◎ of type

    +

    ◎ :: (b -> F c) -> (a -> F b) -> (a -> F c)

    +

    Note: if F = I, ◎ = (.).

    +
    +
    +

    Fix Composition (1/2)

    +

    Goal, find: ◎ :: (b -> F c) -> (a -> F b) -> (a -> F c)
    f :: a -> F b, g :: b -> F c:

    +
      +
    • (g ◎ f) x ???
    • +
    • First apply f to xf x :: F b
    • +
    • Then how to apply g properly to an element of type F b?
    • +
    +
    +
    +

    Fix Composition (2/2)

    +

    Goal, find: ◎ :: (b -> F c) -> (a -> F b) -> (a -> F c)
    f :: a -> F b, g :: b -> F c, f x :: F b:

    +
      +
    • Use fmap :: (t -> u) -> (F t -> F u)!
    • +
    • (fmap g) :: F b -> F (F c) ; (t=b, u=F c)
    • +
    • (fmap g) (f x) :: F (F c) it almost WORKS!
    • +
    • We lack an important component, join :: F (F c) -> F c
    • +
    • (g ◎ f) x = join ((fmap g) (f x))
      ◎ is the Kleisli composition; in Haskell: <=< (in Control.Monad).
    • +
    +
    +
    +

    Necessary laws

    +

    For ◎ to work like composition, we need join to hold the following properties:

    +
      +
    • join (join (F (F (F a))))=join (F (join (F (F a))))
    • +
    • abusing notations denoting join by ⊙; this is equivalent to
      (F ⊙ F) ⊙ F = F ⊙ (F ⊙ F)
    • +
    • There exists η :: a -> F a s.t.
      η⊙F=F=F⊙η
    • +
    +
    +
    +

    Klesli composition

    +

    Now the composition works as expected. In Haskell ◎ is <=< in Control.Monad.

    +

    g <=< f = \x -> join ((fmap g) (f x))

    +
    f x = [x]       ⇒ f 1 = [1]   ⇒ (f <=< f) 1 = [1] ✓
    +g x = [x+1]     ⇒ g 1 = [2]   ⇒ (g <=< g) 1 = [3] ✓
    +h x = [x+1,x*3] ⇒ h 1 = [2,3] ⇒ (h <=< h) 1 = [3,6,4,9] ✓
    + +
    +
    +

    We reinvented Monads!

    +

    A monad is a triplet (M,⊙,η) where

    +
      +
    • \(M\) an Endofunctor (to type a associate M a)
    • +
    • \(⊙:M×M→M\) a nat. trans. (i.e. ⊙::M (M a) → M a ; join)
    • +
    • \(η:I→M\) a nat. trans. (\(I\) identity functor ; η::a → M a)
    • +
    +

    Satisfying

    +
      +
    • \(M ⊙ (M ⊙ M) = (M ⊙ M) ⊙ M\)
    • +
    • \(η ⊙ M = M = M ⊙ η\)
    • +
    +
    +
    +

    Compare with Monoid

    +

    A Monoid is a triplet \((E,∙,e)\) s.t.

    +
      +
    • \(E\) a set
    • +
    • \(∙:E×E→E\)
    • +
    • \(e:1→E\)
    • +
    +

    Satisfying

    +
      +
    • \(x∙(y∙z) = (x∙y)∙z, ∀x,y,z∈E\)
    • +
    • \(e∙x = x = x∙e, ∀x∈E\)
    • +
    +
    +
    +

    Monads are just Monoids

    +
    +

    A Monad is just a monoid in the category of endofunctors, what's the problem?

    +
    +

    The real sentence was:

    +
    +

    All told, a monad in X is just a monoid in the category of endofunctors of X, with product × replaced by composition of endofunctors and unit set by the identity endofunctor.

    +
    +
    +
    +

    Example: List

    +
      +
    • [] :: * -> * an Endofunctor
    • +
    • \(⊙:M×M→M\) a nat. trans. (join :: M (M a) -> M a)
    • +
    • \(η:I→M\) a nat. trans.
    • +
    +
    -- In Haskell ⊙ is "join" in "Control.Monad"
    +join :: [[a]] -> [a]
    +join = concat
    +
    +-- In Haskell the "return" function (unfortunate name)
    +η :: a -> [a]
    +η x = [x]
    + +
    +
    +

    Example: List (law verification)

    +

    Example: List is a functor (join is ⊙)

    +
      +
    • \(M ⊙ (M ⊙ M) = (M ⊙ M) ⊙ M\)
    • +
    • \(η ⊙ M = M = M ⊙ η\)
    • +
    +
    join [ join [[x,y,...,z]] ] = join [[x,y,...,z]]
    +                            = join (join [[[x,y,...,z]]])
    +join (η [x]) = [x] = join [η x]
    + +

    Therefore ([],join,η) is a monad.

    +
    +
    +

    Monads useful?

    +

    A LOT of monad tutorial on the net. Just one example; the State Monad

    +

    DrawScene to State Screen DrawScene ; still pure.

    +
    main = drawImage (width,height)
    +
    +drawImage :: Screen -> DrawScene
    +drawImage screen = do
    +    drawPoint p screen
    +    drawCircle c screen
    +    drawRectangle r screen
    +
    +drawPoint point screen = ...
    +drawCircle circle screen = ...
    +drawRectangle rectangle screen = ...
    +
    main = do
    +    put (Screen 1024 768)
    +    drawImage
    +
    +drawImage :: State Screen DrawScene
    +drawImage = do
    +    drawPoint p
    +    drawCircle c
    +    drawRectangle r
    +
    +drawPoint :: Point ->
    +               State Screen DrawScene
    +drawPoint p = do
    +    Screen width height <- get
    +    ...
    +
    +
    +

    fold

    +fold +
    +
    +

    κατα-morphism

    +catamorphism +
    +
    +

    κατα-morphism: fold generalization

    +

    acc type of the "accumulator":
    fold :: (acc -> a -> acc) -> acc -> [a] -> acc

    +

    Idea: put the accumulated value inside the type.

    +
    -- Equivalent to fold (+1) 0 "cata"
    +(Cons 'c' (Cons 'a' (Cons 't' (Cons 'a' Nil))))
    +(Cons 'c' (Cons 'a' (Cons 't' (Cons 'a' 0))))
    +(Cons 'c' (Cons 'a' (Cons 't' 1)))
    +(Cons 'c' (Cons 'a' 2))
    +(Cons 'c' 3)
    +4
    + +

    But where are all the informations? (+1) and 0?

    +
    +
    +

    κατα-morphism: Missing Information

    +

    Where is the missing information?

    +
      +
    • Functor operator fmap
    • +
    • Algebra representing the (+1) and also knowing about the 0.
    • +
    +

    First example, make length on [Char]

    +
    +
    +

    κατα-morphism: Type work

    +
    
    +data StrF a = Cons Char a | Nil
    +data Str' = StrF Str'
    +
    +-- generalize the construction of Str to other datatype
    +-- Mu: type fixed point
    +-- Mu :: (* -> *) -> *
    +
    +data Mu f = InF { outF :: f (Mu f) }
    +data Str = Mu StrF
    +
    +-- Example
    +foo=InF { outF = Cons 'f'
    +        (InF { outF = Cons 'o'
    +            (InF { outF = Cons 'o'
    +                (InF { outF = Nil })})})}
    + +
    +
    +

    κατα-morphism: missing information retrieved

    +
    type Algebra f a = f a -> a
    +instance Functor (StrF a) =
    +    fmap f (Cons c x) = Cons c (f x)
    +    fmap _ Nil = Nil
    + +
    cata :: Functor f => Algebra f a -> Mu f -> a
    +cata f = f . fmap (cata f) . outF
    + +
    +
    +

    κατα-morphism: Finally length

    +

    All needed information for making length.

    +
    instance Functor (StrF a) =
    +    fmap f (Cons c x) = Cons c (f x)
    +    fmap _ Nil = Nil
    +
    +length' :: Str -> Int
    +length' = cata phi where
    +    phi :: Algebra StrF Int -- StrF Int -> Int
    +    phi (Cons a b) = 1 + b
    +    phi Nil = 0
    +
    +main = do
    +    l <- length' $ stringToStr "Toto"
    +    ...
    +
    +
    +

    κατα-morphism: extension to Trees

    +

    Once you get the trick, it is easy to extent to most Functor.

    +
    type Tree = Mu TreeF
    +data TreeF x = Node Int [x]
    +
    +instance Functor TreeF where
    +  fmap f (Node e xs) = Node e (fmap f xs)
    +
    +depth = cata phi where
    +  phi :: Algebra TreeF Int -- TreeF Int -> Int
    +  phi (Node x sons) = 1 + foldr max 0 sons
    +
    +
    +

    Conclusion

    +

    Category Theory oriented Programming:

    +
      +
    • Focus on the type and operators
    • +
    • Extreme generalisation
    • +
    • Better modularity
    • +
    • Better control through properties of types
    • +
    +

    No cat were harmed in the making of this presentation.

    +
    + +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2012-12-12 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/Haskell-Mandelbrot/index.html b/Scratch/fr/blog/Haskell-Mandelbrot/index.html new file mode 100644 index 0000000..e0293f5 --- /dev/null +++ b/Scratch/fr/blog/Haskell-Mandelbrot/index.html @@ -0,0 +1,224 @@ + + + + + + YBlog - Mandelbrot avec haskell + + + + + + + + + + + + + +
    + + +
    +

    Mandelbrot avec haskell

    +
    +
    +
    +
    +

    Voici le code “obfusqué” :

    +
    a=27;b=79;c=C(-2.0,-1.0);d=C(1.0,1.0);e=C(-2.501,-1.003)
    +newtype C = C (Double,Double) deriving (Show,Eq)
    +instance Num C where C(x,y)*C(z,t)=C(z*x-y*t,y*z+x*t);C(x,y)+C(z,t)=C(x+z,y+t);abs(C(x,y))=C(sqrt(x*x+y*y),0.0)
    +r(C(x,y))=x;i(C(x,y))=y
    +f c z 0=0;f c z n=if(r(abs(z))>2)then n else f c ((z*z)+c) (n-1)
    +h j k = map (\z->(f (C z) (C(0,0)) 32,(fst z>l - q/2))) [(x,y)|y<-[p,(p+((o-p)/a))..o],x<-[m,(m + q)..l]] where o=i k;p=i j;m=r j;l=r k;q=(l-m)/b
    +u j k = concat $ map v $ h j k where v (i,p)=(" .,`'°\":;-+oO0123456789=!%*§&$@#"!!i):rst p;rst True="\n";rst False=""
    +main = putStrLn $ im 0 where cl n (C (x,y))=let cs=(1.1**n-1) in C ((x+cs*(r e))/cs+1,(y+cs*(i e))/cs+1);bl n=cl n c;tr n=cl n d;im n=u (bl n) (tr n)++"\x1b[H\x1b[25A"++im (n+1)
    +

    Pour le lancer, haskell doit être installé. Puis vous devez écrire dans un terminal :

    +

    ghc –make animandel.hs && animandel

    +

    Voici le résultat après 50 itérations.

    +
    +###@@@@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&&&&WWOOClbUOWW&&$$$$$$$$$$$$$$
    +##@@@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&&&&WWUCUb; ,jUOWW&&&$$$$$$$$$$$$
    +#@@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&&WWWWWUb       ooCWW&&&&&&$$$$$$$$
    +@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&WWWWWWWWOU         uUOWWWW&&&&&&$$$$$
    +@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&&WOUObUOOOUUUCbi      rbCUUUOWWWWWOUW&$$$
    +@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&&&&&WWWUcr,iiCb                o wUUUUUC;OW&$$
    +$$$$$$$$$$$$$$$$$$$$$$$$$$&&&&&&&&&&WWWWOUC,                         j    llW&&$
    +$$$$$$$$$$$$$$$$$$$$$&&&&&&&&&&&&WWWWWWOCCbi                              bWWW&&
    +$$$$$$$$$$$$$$$$$&&WWWWWWW&&&WWWWWWWWOUo                                 jUOWW&&
    +$$$$$$$$$$$$$$&&&WWOwOOWWWOUUOWWWWWOOUbw                                  j.blW&
    +$$$$$$$$$$$&&&&&WWWObiijbUCl bCiUUUUUCj,                                    bOW&
    +$$$$$$$$$&&&&&&&WWWOUbw  ;      oobCbl                                     jUWW&
    +$$$$$$$&&&&&&&WWWWOcbi             ij                                      jUW&&
    +$$$$$&&WWWWWWWOwUUCbw                                                       WW&&
    +WWWOWWWWWWWWWUUbo                                                         UWWW&&
    +:                                                                      wbUOWW&&&
    +WWWOWWWWWWWWWUUbo                                                         UWWW&&
    +$$$$$&&WWWWWWWOwUUCbw                                                       WW&&
    +$$$$$$$&&&&&&&WWWWOcbi             ij                                      jUW&&
    +$$$$$$$$$&&&&&&&WWWOUbw  ;      oobCbl                                     jUWW&
    +$$$$$$$$$$$&&&&&WWWObiijbUCl bCiUUUUUCj,                                    bOW&
    +$$$$$$$$$$$$$$&&&WWOwOOWWWOUUOWWWWWOOUbw                                  j.blW&
    +$$$$$$$$$$$$$$$$$&&WWWWWWW&&&WWWWWWWWOUo                                 jUOWW&&
    +$$$$$$$$$$$$$$$$$$$$$&&&&&&&&&&&&WWWWWWOCCbi                              bWWW&&
    +$$$$$$$$$$$$$$$$$$$$$$$$$$&&&&&&&&&&WWWWOUC,                         j    llW&&$
    +@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&&&&&WWWUcr,iiCb                o wUUUUUC;OW&$$
    +
    + +

    Here is the more readable version. I believe with this far more readable version, no more explanation is needed.

    +
    nbvert = 30
    +nbhor = 79
    +zoomfactor = 1.01
    +init_bottom_left = C (-2.0,-2.0)
    +init_top_right   = C (3.0,2.0)
    +interrest        = C (-1.713,-0.000)
    +
    +newtype Complex = C (Float,Float) deriving (Show,Eq)
    +instance Num Complex where
    +    fromInteger n     = C (fromIntegral n,0.0)
    +    C (x,y) * C (z,t) = C (z*x - y*t, y*z + x*t)
    +    C (x,y) + C (z,t) = C (x+z, y+t)
    +    abs (C (x,y))     = C (sqrt (x*x + y*y),0.0)
    +    signum (C (x,y))  = C (signum x , 0.0)
    +
    +real :: Complex -> Float
    +real (C (x,y))    = x
    +im :: Complex -> Float
    +im   (C (x,y))    = y
    +
    +cabs :: Complex -> Float
    +cabs = real.abs
    +
    +f :: Complex -> Complex -> Int -> Int
    +f c z 0 = 0
    +f c z n = if (cabs z > 2) then n else f c ((z*z)+c) (n-1) 
    +
    +bmandel bottomleft topright = map (\z -> (f (C z) (C(0,0)) 32, (fst z > right - hstep/2 ))) [(x,y) | y <- [bottom,(bottom + vstep)..top], x<-[left,(left + hstep)..right]]
    +    where
    +        top = im topright
    +        bottom = im bottomleft
    +        left = real bottomleft
    +        right = real topright
    +        vstep=(top-bottom)/nbvert
    +        hstep=(right-left)/nbhor
    +
    +mandel :: (Complex,Complex) -> String
    +mandel (bottomleft,topright) = concat $ map treat $ bmandel bottomleft topright
    +    where
    +        treat (i,jump) = " .,:;rcuowijlbCUOW&$@#" !! (div (i*22) 32):rst jump
    +        rst True = "\n"
    +        rst False = ""
    +
    +cdiv :: Complex -> Float -> Complex
    +cdiv (C(x,y)) r = C(x/r, y/r) 
    +cmul :: Complex -> Float -> Complex
    +cmul (C(x,y)) r = C(x*r, y*r) 
    +
    +zoom :: Complex -> Complex -> Complex -> Float -> (Complex,Complex)
    +zoom bl tr center magn = (f bl, f tr)
    +    where
    +        f point = ((center `cmul` magn) + point ) `cdiv` (magn + 1)
    +
    +main = do
    +    x <- getContents
    +    putStrLn $ infinitemandel 0
    +    where
    +        window n = zoom init_bottom_left init_top_right interrest (zoomfactor**n) 
    +        infinitemandel n = mandel (window n) ++ "\x1b[H\x1b[25A" ++ infinitemandel (n+1)
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-07-10 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/index.html b/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/index.html new file mode 100644 index 0000000..5593bdc --- /dev/null +++ b/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/index.html @@ -0,0 +1,1307 @@ + + + + + + YBlog - Un example progressif avec Haskell + + + + + + + + + + + + + +
    + + +
    +

    Un example progressif avec Haskell

    +
    +
    +
    +
    +

    The B in Benoît B. Mandelbrot stand for Benoît B. Mandelbrot

    +
    + +

    tlpl: Un exemple progressif d’utilisation d’Haskell. Vous pourrez voir un ensemble de Mandelbrot étendu à la troisième dimension. De plus le code sera très propre. Les détails de rendu sont séparés dans un module externe. Le code descriptif intéressant est concentré dans un environnement pur et fonctionnel. Vous pouvez vous inspirer de ce code utilisant le paradigme fonctional dans tous les languages.

    +
    + +

    Introduction

    +

    In my preceding article I introduced Haskell.

    +

    This article goes further. It will show how to use functional programming with interactive programs. But more than that, it will show how to organize your code in a functional way. This article is more about functional paradigm than functional language. The code organization can be used in most imperative language.

    +

    As Haskell is designed for functional paradigm, it is easier to use in this context. In reality, the firsts sections will use an imperative paradigm. As you can use functional paradigm in imperative language, you can also use imperative paradigm in functional languages.

    +

    This article is about creating an useful and clean program. It can interact with the user in real time. It uses OpenGL, a library with imperative programming foundations. Despite this fact, most of the final code will remain in the pure part (no IO).

    +

    I believe the main audience for this article are:

    +
      +
    • Haskell programmer looking for an OpengGL tutorial.
    • +
    • People interested in program organization (programming language agnostic).
    • +
    • Fractal lovers and in particular 3D fractal.
    • +
    • People interested in user interaction in a functional paradigm.
    • +
    +

    I had in mind for some time now to make a Mandelbrot set explorer. I had already written a command line Mandelbrot set generator in Haskell. This utility is highly parallel; it uses the repa package1.

    +

    This time, we will not parallelize the computation. Instead, we will display the Mandelbrot set extended in 3D using OpenGL and Haskell. You will be able to move it using your keyboard. This object is a Mandelbrot set in the plan (z=0), and something nice to see in 3D.

    +

    Here are some screenshots of the result:

    +
    +The entire Mandelbulb +
    +The entire Mandelbulb +
    +
    +A Mandelbulb detail +
    +A Mandelbulb detail +
    +
    +Another detail of the Mandelbulb +
    +Another detail of the Mandelbulb +
    + +

    And you can see the intermediate steps to reach this goal:

    +

    The parts of the article

    +

    From the 2nd section to the 4th it will be dirtier and dirtier. We start cleaning the code at the 5th section.

    +
    +

    Download the source code of this section → 01_Introduction/hglmandel.lhs

    +

    First version

    +

    We can consider two parts. The first being mostly some boilerplate2. And the second part more focused on OpenGL and content.

    +

    Let’s play the song of our people

    +
    +
    import Graphics.Rendering.OpenGL
    +import Graphics.UI.GLUT
    +import Data.IORef
    +
    + +

    For efficiency reason3, I will not use the default Haskell Complex data type.

    +
    +
    data Complex = C (Float,Float) deriving (Show,Eq)
    +
    + +
    +
    instance Num Complex where
    +    fromInteger n = C (fromIntegral n,0.0)
    +    C (x,y) * C (z,t) = C (z*x - y*t, y*z + x*t)
    +    C (x,y) + C (z,t) = C (x+z, y+t)
    +    abs (C (x,y))     = C (sqrt (x*x + y*y),0.0)
    +    signum (C (x,y))  = C (signum x , 0.0)
    +
    + +

    We declare some useful functions for manipulating complex numbers:

    +
    +
    complex :: Float -> Float -> Complex
    +complex x y = C (x,y)
    +
    +real :: Complex -> Float
    +real (C (x,y))    = x
    +
    +im :: Complex -> Float
    +im   (C (x,y))    = y
    +
    +magnitude :: Complex -> Float
    +magnitude = real.abs
    +
    + +

    Let us start

    +

    We start by giving the main architecture of our program:

    +
    +
    main :: IO ()
    +main = do
    +  -- GLUT need to be initialized
    +  (progname,_) <- getArgsAndInitialize
    +  -- We will use the double buffered mode (GL constraint)
    +  initialDisplayMode $= [DoubleBuffered]
    +  -- We create a window with some title
    +  createWindow "Mandelbrot Set with Haskell and OpenGL"
    +  -- Each time we will need to update the display
    +  -- we will call the function 'display'
    +  displayCallback $= display
    +  -- We enter the main loop
    +  mainLoop
    +
    + +

    Mainly, we initialize our OpenGL application. We declared that the function display will be used to render the graphics:

    +
    +
    display = do
    +  clear [ColorBuffer] -- make the window black
    +  loadIdentity -- reset any transformation
    +  preservingMatrix drawMandelbrot
    +  swapBuffers -- refresh screen
    +
    + +

    Also here, there is only one interesting line; the draw will occur in the function drawMandelbrot.

    +

    This function will provide a list of draw actions. Remember that OpenGL is imperative by design. Then, one of the consequence is you must write the actions in the right order. No easy parallel drawing here. Here is the function which will render something on the screen:

    +
    +
    drawMandelbrot =
    +  -- We will print Points (not triangles for example) 
    +  renderPrimitive Points $ do
    +    mapM_ drawColoredPoint allPoints
    +  where
    +      drawColoredPoint (x,y,c) = do
    +          color c -- set the current color to c
    +          -- then draw the point at position (x,y,0)
    +          -- remember we're in 3D
    +          vertex $ Vertex3 x y 0 
    +
    + +

    The mapM_ function is mainly the same as map but inside a monadic context. More precisely, this can be transformed as a list of actions where the order is important:

    +
    drawMandelbrot = 
    +  renderPrimitive Points $ do
    +    color color1
    +    vertex $ Vertex3 x1 y1 0
    +    ...
    +    color colorN
    +    vertex $ Vertex3 xN yN 0
    +

    We also need some kind of global variables. In fact, global variable are a proof of a design problem. We will get rid of them later.

    +
    +
    width = 320 :: GLfloat
    +height = 320 :: GLfloat
    +
    + +

    And of course our list of colored points. In OpenGL the default coordinate are from -1 to 1.

    +
    +
    allPoints :: [(GLfloat,GLfloat,Color3 GLfloat)]
    +allPoints = [ (x/width,y/height,colorFromValue $ mandel x y) | 
    +                  x <- [-width..width], 
    +                  y <- [-height..height]]
    +
    + +

    We need a function which transform an integer value to some color:

    +
    +
    colorFromValue n =
    +  let 
    +      t :: Int -> GLfloat
    +      t i = 0.5 + 0.5*cos( fromIntegral i / 10 )
    +  in
    +    Color3 (t n) (t (n+5)) (t (n+10))
    +
    + +

    And now the mandel function. Given two coordinates in pixels, it returns some integer value:

    +
    +
    mandel x y = 
    +  let r = 2.0 * x / width
    +      i = 2.0 * y / height
    +  in
    +      f (complex r i) 0 64
    +
    + +

    It uses the main Mandelbrot function for each complex \(c\). The Mandelbrot set is the set of complex number \(c\) such that the following sequence does not escape to infinity.

    +

    Let us define \(f_c: \)

    +


    fc(z) = z2 + c

    +

    The sequence is:

    +


    0 → fc(0) → fc(fc(0)) → ⋯ → fcn(0) → ⋯

    +

    Of course, instead of trying to test the real limit, we just make a test after a finite number of occurrences.

    +
    +
    f :: Complex -> Complex -> Int -> Int
    +f c z 0 = 0
    +f c z n = if (magnitude z > 2 ) 
    +          then n
    +          else f c ((z*z)+c) (n-1)
    +
    + +

    Well, if you download this file (look at the bottom of this section), compile it and run it this is the result:

    +

    The mandelbrot set version 1

    +

    A first very interesting property of this program is that the computation for all the points is done only once. It is a bit long before the first image appears, but if you resize the window, it updates instantaneously. This property is a direct consequence of purity. If you look closely, you see that allPoints is a pure list. Therefore, calling allPoints will always render the same result and Haskell is clever enough to use this property. While Haskell doesn’t garbage collect allPoints the result is reused for free. We did not specified this value should be saved for later use. It is saved for us.

    +

    See what occurs if we make the window bigger:

    +

    The mandelbrot too wide, black lines and columns

    +

    We see some black lines because we have drawn less point than there is on the surface. We can repair this by drawing little squares instead of just points. But, instead we will do something a bit different and unusual.

    +

    Download the source code of this section → 01_Introduction/hglmandel.lhs

    +
    +

    Download the source code of this section → 02_Edges/HGLMandelEdge.lhs

    +

    Only the edges

    +
    + +
    +
    import Graphics.Rendering.OpenGL
    +import Graphics.UI.GLUT
    +import Data.IORef
    +-- Use UNPACK data because it is faster
    +-- The ! is for strict instead of lazy
    +data Complex = C  {-# UNPACK #-} !Float 
    +                  {-# UNPACK #-} !Float 
    +               deriving (Show,Eq)
    +instance Num Complex where
    +    fromInteger n = C (fromIntegral n) 0.0
    +    (C x y) * (C z t) = C (z*x - y*t) (y*z + x*t)
    +    (C x y) + (C z t) = C (x+z) (y+t)
    +    abs (C x y)     = C (sqrt (x*x + y*y)) 0.0
    +    signum (C x y)  = C (signum x) 0.0
    +complex :: Float -> Float -> Complex
    +complex x y = C x y
    +
    +real :: Complex -> Float
    +real (C x y)    = x
    +
    +im :: Complex -> Float
    +im   (C x y)    = y
    +
    +magnitude :: Complex -> Float
    +magnitude = real.abs
    +main :: IO ()
    +main = do
    +  -- GLUT need to be initialized
    +  (progname,_) <- getArgsAndInitialize
    +  -- We will use the double buffered mode (GL constraint)
    +  initialDisplayMode $= [DoubleBuffered]
    +  -- We create a window with some title
    +  createWindow "Mandelbrot Set with Haskell and OpenGL"
    +  -- Each time we will need to update the display
    +  -- we will call the function 'display'
    +  displayCallback $= display
    +  -- We enter the main loop
    +  mainLoop
    +display = do
    +   -- set the background color (dark solarized theme)
    +  clearColor $= Color4 0 0.1686 0.2117 1
    +  clear [ColorBuffer] -- make the window black
    +  loadIdentity -- reset any transformation
    +  preservingMatrix drawMandelbrot
    +  swapBuffers -- refresh screen
    +
    +width = 320 :: GLfloat
    +height = 320 :: GLfloat
    +
    + +
    + +

    This time, instead of drawing all points, we will simply draw the edges of the Mandelbrot set. The method I use is a rough approximation. I consider the Mandelbrot set to be almost convex. The result will be good enough for the purpose of this tutorial.

    +

    We change slightly the drawMandelbrot function. We replace the Points by LineLoop

    +
    +
    drawMandelbrot =
    +  -- We will print Points (not triangles for example) 
    +  renderPrimitive LineLoop $ do
    +    mapM_ drawColoredPoint allPoints
    +  where
    +      drawColoredPoint (x,y,c) = do
    +          color c -- set the current color to c
    +          -- then draw the point at position (x,y,0)
    +          -- remember we're in 3D
    +          vertex $ Vertex3 x y 0 
    +
    + +

    And now, we should change our list of points. Instead of drawing every point of the visible surface, we will choose only point on the surface.

    +
    +
    allPoints = positivePoints ++ 
    +      map (\(x,y,c) -> (x,-y,c)) (reverse positivePoints)
    +
    + +

    We only need to compute the positive point. The Mandelbrot set is symmetric relatively to the abscisse axis.

    +
    +
    positivePoints :: [(GLfloat,GLfloat,Color3 GLfloat)]
    +positivePoints = do
    +     x <- [-width..width]
    +     let y = maxZeroIndex (mandel x) 0 height (log2 height)
    +     if y < 1 -- We don't draw point in the absciss
    +        then []
    +        else return (x/width,y/height,colorFromValue $ mandel x y)
    +     where
    +         log2 n = floor ((log n) / log 2)
    +
    + +

    This function is interesting. For those not used to the list monad here is a natural language version of this function:

    +
    positivePoints =
    +    for all x in the range [-width..width]
    +    let y be smallest number s.t. mandel x y > 0
    +    if y is on 0 then don't return a point
    +    else return the value corresonding to (x,y,color for (x+iy))
    +

    In fact using the list monad you write like if you consider only one element at a time and the computation is done non deterministically. To find the smallest number such that mandel x y > 0 we use a simple dichotomy:

    +
    +
    -- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex func minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if (func medpoint) /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    + +

    No rocket science here. See the result now:

    +

    The edges of the mandelbrot set

    +
    + +
    +
    colorFromValue n =
    +  let 
    +      t :: Int -> GLfloat
    +      t i = 0.5 + 0.5*cos( fromIntegral i / 10 )
    +  in
    +    Color3 (t n) (t (n+5)) (t (n+10))
    +
    + +
    +
    mandel x y = 
    +  let r = 2.0 * x / width
    +      i = 2.0 * y / height
    +  in
    +      f (complex r i) 0 64
    +
    + +
    +
    f :: Complex -> Complex -> Int -> Int
    +f c z 0 = 0
    +f c z n = if (magnitude z > 2 ) 
    +          then n
    +          else f c ((z*z)+c) (n-1)
    +
    + +
    + +

    Download the source code of this section → 02_Edges/HGLMandelEdge.lhs

    +
    +

    Download the source code of this section → 03_Mandelbulb/Mandelbulb.lhs

    +

    3D Mandelbrot?

    +

    Now we will we extend to a third dimension. But, there is no 3D equivalent to complex. In fact, the only extension known are quaternions (in 4D). As I know almost nothing about quaternions, I will use some extended complex, instead of using a 3D projection of quaternions. I am pretty sure this construction is not useful for numbers. But it will be enough for us to create something that look nice.

    +

    This section is quite long, but don’t be afraid, most of the code is some OpenGL boilerplate. If you just want to skim this section, here is a high level representation:

    +
    +
      +
    • OpenGL Boilerplate

    • +
    • set some IORef (understand variables) for states
    • +
    • Drawing:

      +
        +
      • set doubleBuffer, handle depth, window size…
      • +
      • Use state to apply some transformations
      • +
    • +
    • Keyboard: hitting some key change the state of IORef

    • +
    • Generate 3D Object

    • +
    +

    ~ allPoints :: [ColoredPoint]
    allPoints = for all (x,y), -width 0 add the points (x, y, z,color) (x,-y, z,color) (x, y,-z,color) (x,-y,-z,color) + neighbors to make triangles ~

    +
    +
    + +
    +
    import Graphics.Rendering.OpenGL
    +import Graphics.UI.GLUT
    +import Data.IORef
    +type ColoredPoint = (GLfloat,GLfloat,GLfloat,Color3 GLfloat)
    +
    + +
    + +

    We declare a new type ExtComplex (for extended complex). An extension of complex numbers with a third component:

    +
    +
    data ExtComplex = C (GLfloat,GLfloat,GLfloat) 
    +                  deriving (Show,Eq)
    +instance Num ExtComplex where
    +    -- The shape of the 3D mandelbrot 
    +    -- will depend on this formula
    +    C (x,y,z) * C (x',y',z') = C (x*x' - y*y' - z*z', 
    +                                  x*y' + y*x' + z*z', 
    +                                  x*z' + z*x' )
    +    -- The rest is straightforward
    +    fromInteger n = C (fromIntegral n, 0, 0)
    +    C (x,y,z) + C (x',y',z') = C (x+x', y+y', z+z')
    +    abs (C (x,y,z))     = C (sqrt (x*x + y*y + z*z), 0, 0)
    +    signum (C (x,y,z))  = C (signum x, signum y, signum z)
    +
    + +

    The most important part is the new multiplication instance. Modifying this formula will change radically the shape of the result. Here is the formula written in a more mathematical notation. I called the third component of these extended complex strange.

    +


    real((x, y, z) * (xʹ, yʹ, zʹ)) = xxʹ − yyʹ − zzʹ

    +


    im((x, y, z) * (xʹ, yʹ, zʹ)) = xyʹ − yxʹ + zzʹ

    +


    strange((x, y, z) * (xʹ, yʹ, zʹ)) = xzʹ + zxʹ

    +

    Note how if z=z'=0 then the multiplication is the same to the complex one.

    +
    + +
    +
    extcomplex :: GLfloat -> GLfloat -> GLfloat -> ExtComplex
    +extcomplex x y z = C (x,y,z)
    +
    +real :: ExtComplex -> GLfloat
    +real (C (x,y,z))    = x
    +
    +im :: ExtComplex -> GLfloat
    +im   (C (x,y,z))    = y
    +
    +strange :: ExtComplex -> GLfloat
    +strange (C (x,y,z)) = z
    +
    +magnitude :: ExtComplex -> GLfloat
    +magnitude = real.abs
    +
    + +
    + +

    From 2D to 3D

    +

    As we will use some 3D, we add some new directive in the boilerplate. But mainly, we simply state that will use some depth buffer. And also we will listen the keyboard.

    +
    +
    main :: IO ()
    +main = do
    +  -- GLUT need to be initialized
    +  (progname,_) <- getArgsAndInitialize
    +  -- We will use the double buffered mode (GL constraint)
    +  -- We also Add the DepthBuffer (for 3D)
    +  initialDisplayMode $= 
    +      [WithDepthBuffer,DoubleBuffered,RGBMode]
    +  -- We create a window with some title
    +  createWindow "3D HOpengGL Mandelbrot"
    +  -- We add some directives
    +  depthFunc  $= Just Less
    +  windowSize $= Size 500 500
    +  -- Some state variables (I know it feels BAD)
    +  angle   <- newIORef ((35,0)::(GLfloat,GLfloat))
    +  zoom    <- newIORef (2::GLfloat)
    +  campos  <- newIORef ((0.7,0)::(GLfloat,GLfloat))
    +  -- Function to call each frame
    +  idleCallback $= Just idle
    +  -- Function to call when keyboard or mouse is used
    +  keyboardMouseCallback $= 
    +          Just (keyboardMouse angle zoom campos)
    +  -- Each time we will need to update the display
    +  -- we will call the function 'display'
    +  -- But this time, we add some parameters
    +  displayCallback $= display angle zoom campos
    +  -- We enter the main loop
    +  mainLoop
    +
    + +

    The idle is here to change the states. There should never be any modification done in the display function.

    +
    +
    idle = postRedisplay Nothing
    +
    + +

    We introduce some helper function to manipulate standard IORef. Mainly modVar x f is equivalent to the imperative x:=f(x), modFst (x,y) (+1) is equivalent to (x,y) := (x+1,y) and modSnd (x,y) (+1) is equivalent to (x,y) := (x,y+1)

    +
    +
    modVar v f = do
    +  v' <- get v
    +  v $= (f v')
    +mapFst f (x,y) = (f x,  y)
    +mapSnd f (x,y) = (  x,f y)
    +
    + +

    And we use them to code the function handling keyboard. We will use the keys hjkl to rotate, oi to zoom and sedf to move. Also, hitting space will reset the view. Remember that angle and campos are pairs and zoom is a scalar. Also note (+0.5) is the function \x->x+0.5 and (-0.5) is the number -0.5 (yes I share your pain).

    +
    +
    keyboardMouse angle zoom campos key state modifiers position =
    +  -- We won't use modifiers nor position
    +  kact angle zoom campos key state
    +  where 
    +    -- reset view when hitting space
    +    kact a z p (Char ' ') Down = do
    +          a $= (0,0) -- angle 
    +          z $= 1     -- zoom
    +          p $= (0,0) -- camera position
    +    -- use of hjkl to rotate
    +    kact a _ _ (Char 'h') Down = modVar a (mapFst (+0.5))
    +    kact a _ _ (Char 'l') Down = modVar a (mapFst (+(-0.5)))
    +    kact a _ _ (Char 'j') Down = modVar a (mapSnd (+0.5))
    +    kact a _ _ (Char 'k') Down = modVar a (mapSnd (+(-0.5)))
    +    -- use o and i to zoom
    +    kact _ z _ (Char 'o') Down = modVar z (*1.1)
    +    kact _ z _ (Char 'i') Down = modVar z (*0.9)
    +    -- use sdfe to move the camera
    +    kact _ _ p (Char 's') Down = modVar p (mapFst (+0.1))
    +    kact _ _ p (Char 'f') Down = modVar p (mapFst (+(-0.1)))
    +    kact _ _ p (Char 'd') Down = modVar p (mapSnd (+0.1))
    +    kact _ _ p (Char 'e') Down = modVar p (mapSnd (+(-0.1)))
    +    -- any other keys does nothing
    +    kact _ _ _ _ _ = return ()
    +
    + +

    Note display takes some parameters this time. This function if full of boilerplate:

    +
    +
    display angle zoom position = do
    +   -- set the background color (dark solarized theme)
    +  clearColor $= Color4 0 0.1686 0.2117 1
    +  clear [ColorBuffer,DepthBuffer]
    +  -- Transformation to change the view
    +  loadIdentity -- reset any transformation
    +  -- tranlate
    +  (x,y) <- get position
    +  translate $ Vector3 x y 0 
    +  -- zoom
    +  z <- get zoom
    +  scale z z z
    +  -- rotate
    +  (xangle,yangle) <- get angle
    +  rotate xangle $ Vector3 1.0 0.0 (0.0::GLfloat)
    +  rotate yangle $ Vector3 0.0 1.0 (0.0::GLfloat)
    +
    +  -- Now that all transformation were made
    +  -- We create the object(s)
    +  preservingMatrix drawMandelbrot
    +
    +  swapBuffers -- refresh screen
    +
    + +

    Not much to say about this function. Mainly there are two parts: apply some transformations, draw the object.

    +

    The 3D Mandelbrot

    +

    We have finished with the OpenGL section, let’s talk about how we generate the 3D points and colors. First, we will set the number of details to 200 pixels in the three dimensions.

    +
    +
    nbDetails = 200 :: GLfloat
    +width  = nbDetails
    +height = nbDetails
    +deep   = nbDetails
    +
    + +

    This time, instead of just drawing some line or some group of points, we will show triangles. The function allPoints will provide a multiple of three points. Each three successive point representing the coordinate of each vertex of a triangle.

    +
    +
    drawMandelbrot = do
    +  -- We will print Points (not triangles for example) 
    +  renderPrimitive Triangles $ do
    +    mapM_ drawColoredPoint allPoints
    +  where
    +      drawColoredPoint (x,y,z,c) = do
    +          color c
    +          vertex $ Vertex3 x y z
    +
    + +

    In fact, we will provide six ordered points. These points will be used to draw two triangles.

    +

    Explain triangles

    +

    The next function is a bit long. Here is an approximative English version:

    +
    forall x from -width to width
    +  forall y from -height to height
    +    forall the neighbors of (x,y)
    +      let z be the smalled depth such that (mandel x y z)>0
    +      let c be the color given by mandel x y z 
    +      add the point corresponding to (x,y,z,c)
    +

    Also, I added a test to hide points too far from the border. In fact, this function show points close to the surface of the modified mandelbrot set. But not the mandelbrot set itself.

    +
    depthPoints :: [ColoredPoint]
    +depthPoints = do
    +  x <- [-width..width]
    +  y <- [-height..height]
    +  let 
    +      depthOf x' y' = maxZeroIndex (mandel x' y') 0 deep logdeep 
    +      logdeep = floor ((log deep) / log 2)
    +      z1 = depthOf    x     y
    +      z2 = depthOf (x+1)    y
    +      z3 = depthOf (x+1) (y+1)
    +      z4 = depthOf    x  (y+1)
    +      c1 = mandel    x     y  (z1+1)
    +      c2 = mandel (x+1)    y  (z2+1)
    +      c3 = mandel (x+1) (y+1) (z3+1)
    +      c4 = mandel    x  (y+1) (z4+1)
    +      p1 = (   x /width,   y /height, z1/deep, colorFromValue c1)
    +      p2 = ((x+1)/width,   y /height, z2/deep, colorFromValue c2)
    +      p3 = ((x+1)/width,(y+1)/height, z3/deep, colorFromValue c3)
    +      p4 = (   x /width,(y+1)/height, z4/deep, colorFromValue c4)
    +  if (and $ map (>=57) [c1,c2,c3,c4])
    +  then []
    +  else [p1,p2,p3,p1,p3,p4]
    +

    If you look at the function above, you see a lot of common patterns. Haskell is very efficient to make this better. Here is a harder to read but shorter and more generic rewritten function:

    +
    +
    depthPoints :: [ColoredPoint]
    +depthPoints = do
    +  x <- [-width..width]
    +  y <- [-height..height]
    +  let 
    +    neighbors = [(x,y),(x+1,y),(x+1,y+1),(x,y+1)]
    +    depthOf (u,v) = maxZeroIndex (mandel u v) 0 deep logdeep
    +    logdeep = floor ((log deep) / log 2)
    +    -- zs are 3D points with found depth
    +    zs = map (\(u,v) -> (u,v,depthOf (u,v))) neighbors
    +    -- ts are 3D pixels + mandel value
    +    ts = map (\(u,v,w) -> (u,v,w,mandel u v (w+1))) zs
    +    -- ps are 3D opengl points + color value
    +    ps = map (\(u,v,w,c') -> 
    +        (u/width,v/height,w/deep,colorFromValue c')) ts
    +  -- If the point diverged too fast, don't display it
    +  if (and $ map (\(_,_,_,c) -> c>=57) ts)
    +  then []
    +  -- Draw two triangles
    +  else [ps!!0,ps!!1,ps!!2,ps!!0,ps!!2,ps!!3]
    +
    + +

    If you prefer the first version, then just imagine how hard it will be to change the enumeration of the point from (x,y) to (x,z) for example.

    +

    Also, we didn’t searched for negative values. This modified Mandelbrot is no more symmetric relatively to the plan y=0. But it is symmetric relatively to the plan z=0. Then I mirror these values.

    +
    +
    allPoints :: [ColoredPoint]
    +allPoints = planPoints ++ map inverseDepth  planPoints
    +  where 
    +      planPoints = depthPoints
    +      inverseDepth (x,y,z,c) = (x,y,-z+1/deep,c)
    +
    + +

    The rest of the program is very close to the preceding one.

    +
    + +
    +
    -- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => 
    +                 (a -> b) -> a -> a -> Int -> a
    +maxZeroIndex func minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if (func medpoint) /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    + +

    I made the color slightly brighter

    +
    +
    colorFromValue n =
    +  let 
    +      t :: Int -> GLfloat
    +      t i = 0.7 + 0.3*cos( fromIntegral i / 10 )
    +  in
    +    Color3 (t n) (t (n+5)) (t (n+10))
    +
    + +

    We only changed from Complex to ExtComplex of the main f function.

    +
    +
    f :: ExtComplex -> ExtComplex -> Int -> Int
    +f c z 0 = 0
    +f c z n = if (magnitude z > 2 ) 
    +          then n
    +          else f c ((z*z)+c) (n-1)
    +
    + +
    + +

    We simply add a new dimension to the mandel function and change the type signature of f from Complex to ExtComplex.

    +
    +
    mandel x y z = 
    +  let r = 2.0 * x / width
    +      i = 2.0 * y / height
    +      s = 2.0 * z / deep
    +  in
    +      f (extcomplex r i s) 0 64
    +
    + +

    Here is the result:

    +

    A 3D mandelbrot like

    +

    Download the source code of this section → 03_Mandelbulb/Mandelbulb.lhs

    +
    +

    Download the source code of this section → 04_Mandelbulb/Mandelbulb.lhs

    +

    Naïve code cleaning

    +

    The first approach to clean the code is to separate the GLUT/OpenGL part from the computation of the shape. Here is the cleaned version of the preceding section. Most boilerplate was put in external files.

    + +
    +
    import YBoiler -- Most the OpenGL Boilerplate
    +import Mandel -- The 3D Mandelbrot maths
    +
    + +

    The yMainLoop takes two arguments: the title of the window and a function from time to triangles

    +
    +
    main :: IO ()
    +main = yMainLoop "3D Mandelbrot" (\_ -> allPoints)
    +
    + +

    We set some global constant (this is generally bad).

    +
    +
    nbDetails = 200 :: GLfloat
    +width  = nbDetails
    +height = nbDetails
    +deep   = nbDetails
    +
    + +

    We then generate colored points from our function. This is similar to the preceding section.

    +
    +
    allPoints :: [ColoredPoint]
    +allPoints = planPoints ++ map inverseDepth  planPoints
    +  where 
    +      planPoints = depthPoints ++ map inverseHeight depthPoints
    +      inverseHeight (x,y,z,c) = (x,-y,z,c)
    +      inverseDepth (x,y,z,c) = (x,y,-z+1/deep,c)
    +
    + +
    +
    depthPoints :: [ColoredPoint]
    +depthPoints = do
    +  x <- [-width..width]
    +  y <- [0..height]
    +  let 
    +    neighbors = [(x,y),(x+1,y),(x+1,y+1),(x,y+1)]
    +    depthOf (u,v) = maxZeroIndex (ymandel u v) 0 deep 7
    +    -- zs are 3D points with found depth
    +    zs = map (\(u,v) -> (u,v,depthOf (u,v))) neighbors
    +    -- ts are 3D pixels + mandel value
    +    ts = map (\(u,v,w) -> (u,v,w,ymandel u v (w+1))) zs
    +    -- ps are 3D opengl points + color value
    +    ps = map (\(u,v,w,c') -> 
    +        (u/width,v/height,w/deep,colorFromValue c')) ts
    +  -- If the point diverged too fast, don't display it
    +  if (and $ map (\(_,_,_,c) -> c>=57) ts)
    +  then []
    +  -- Draw two triangles
    +  else [ps!!0,ps!!1,ps!!2,ps!!0,ps!!2,ps!!3]
    +
    +-- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex func minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if (func medpoint) /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    +colorFromValue n =
    +  let 
    +      t :: Int -> GLfloat
    +      t i = 0.7 + 0.3*cos( fromIntegral i / 10 )
    +  in
    +    ((t n),(t (n+5)),(t (n+10)))
    +
    +ymandel x y z = mandel (2*x/width) (2*y/height) (2*z/deep) 64
    +
    + +

    This code is cleaner but many things doesn’t feel right. First, all the user interaction code is outside our main file. I feel it is okay to hide the detail for the rendering. But I would have preferred to control the user actions.

    +

    On the other hand, we continue to handle a lot rendering details. For example, we provide ordered vertices.

    +

    Download the source code of this section → 04_Mandelbulb/Mandelbulb.lhs

    +
    +

    Download the source code of this section → 05_Mandelbulb/Mandelbulb.lhs

    +

    Functional organization?

    +

    Some points:

    +
      +
    1. OpenGL and GLUT is done in C. In particular the mainLoop function is a direct link to the C library (FFI). This function is clearly far from the functional paradigm. Could we make this better? We will have two choices:
    2. +
    +
      +
    • create our own mainLoop function to make it more functional.
    • +
    • deal with the imperative nature of the GLUT mainLoop function.
    • +
    +

    As one of the goal of this article is to understand how to deal with existing libraries and particularly the one coming from imperative languages, we will continue to use the mainLoop function. 2. Our main problem come from user interaction. If you ask “the Internet”, about how to deal with user interaction with a functional paradigm, the main answer is to use functional reactive programming (FRP). I won’t use FRP in this article. Instead, I’ll use a simpler while less effective way to deal with user interaction. But The method I’ll use will be as pure and functional as possible.

    +

    Here is how I imagine things should go. First, what the main loop should look like if we could make our own:

    +
    functionalMainLoop =
    +    Read user inputs and provide a list of actions
    +    Apply all actions to the World
    +    Display one frame 
    +    repetere aeternum
    +

    Clearly, ideally we should provide only three parameters to this main loop function:

    +
      +
    • an initial World state
    • +
    • a mapping between the user interactions and functions which modify the world
    • +
    • a function taking two parameters: time and world state and render a new world without user interaction.
    • +
    +

    Here is a real working code, I’ve hidden most display functions. The YGL, is a kind of framework to display 3D functions. But it can easily be extended to many kind of representation.

    +
    +
    import YGL -- Most the OpenGL Boilerplate
    +import Mandel -- The 3D Mandelbrot maths
    +
    + +

    We first set the mapping between user input and actions. The type of each couple should be of the form (user input, f) where (in a first time) f:World -> World. It means, the user input will transform the world state.

    +
    +
    -- Centralize all user input interaction
    +inputActionMap :: InputMap World
    +inputActionMap = inputMapFromList [
    +     (Press 'k' , rotate xdir   5)
    +    ,(Press 'i' , rotate xdir (-5))
    +    ,(Press 'j' , rotate ydir   5)
    +    ,(Press 'l' , rotate ydir (-5))
    +    ,(Press 'o' , rotate zdir   5)
    +    ,(Press 'u' , rotate zdir (-5))
    +    ,(Press 'f' , translate xdir   0.1)
    +    ,(Press 's' , translate xdir (-0.1))
    +    ,(Press 'e' , translate ydir   0.1)
    +    ,(Press 'd' , translate ydir (-0.1))
    +    ,(Press 'z' , translate zdir   0.1)
    +    ,(Press 'r' , translate zdir (-0.1))
    +    ,(Press '+' , zoom    1.1)
    +    ,(Press '-' , zoom (1/1.1))
    +    ,(Press 'h' , resize    1.2)
    +    ,(Press 'g' , resize (1/1.2))
    +    ]
    +
    + +

    And of course a type design the World State. The important part is that it is our World State type. We could have used any kind of data type.

    +
    +
    -- I prefer to set my own name for these types
    +data World = World {
    +      angle       :: Point3D
    +    , scale       :: Scalar
    +    , position    :: Point3D
    +    , shape       :: Scalar -> Function3D
    +    , box         :: Box3D
    +    , told        :: Time -- last frame time
    +    } 
    +
    + +

    The important part to glue our own type to the framework is to make our type an instance of the type class DisplayableWorld. We simply have to provide the definition of some functions.

    +
    +
    instance DisplayableWorld World where
    +  winTitle _ = "The YGL Mandelbulb"
    +  camera w = Camera {
    +        camPos = position w, 
    +        camDir = angle w,
    +        camZoom = scale w }
    +  -- objects for world w
    +  -- is the list of one unique element
    +  -- The element is an YObject
    +  --   more precisely the XYFunc Function3D Box3D
    +  --   where the Function3D is the type
    +  --             Point -> Point -> Maybe (Point,Color)
    +  --   and its value here is ((shape w) res)
    +  --   and the Box3D value is defbox
    +  objects w = [XYFunc ((shape  w) res) defbox]
    +              where
    +                  res = resolution $ box w
    +                  defbox = box w
    +
    + +

    The camera function will retrieve an object of type Camera which contains most necessary information to set our camera. The objects function will returns a list of objects. Their type is YObject. Note the generation of triangles is no more in this file. Until here we only used declarative pattern.

    +

    We also need to set all our transformation functions. These function are used to update the world state.

    +
    +
    xdir :: Point3D
    +xdir = makePoint3D (1,0,0)
    +ydir :: Point3D
    +ydir = makePoint3D (0,1,0)
    +zdir :: Point3D
    +zdir = makePoint3D (0,0,1)
    +
    + +

    Note (-*<) is the scalar product (α -*< (x,y,z) = (αx,αy,αz)). Also note we could add two Point3D.

    +
    +
    rotate :: Point3D -> Scalar -> World -> World
    +rotate dir angleValue world = 
    +  world {
    +     angle = (angle world) + (angleValue -*< dir) }
    +
    +translate :: Point3D -> Scalar -> World -> World
    +translate dir len world = 
    +  world {
    +    position = (position world) + (len -*< dir) }
    +
    +zoom :: Scalar -> World -> World
    +zoom z world = world {
    +    scale = z * scale world }
    +
    +resize :: Scalar -> World -> World
    +resize r world = world {
    +    box = (box world) {
    +     resolution = sqrt ((resolution (box world))**2 * r) }}
    +
    + +

    The resize is used to generate the 3D function. As I wanted the time spent to generate a more detailed view to grow linearly I use this not so straightforward formula.

    +

    The yMainLoop takes three arguments.

    +
      +
    • A map between user Input and world transformation
    • +
    • A timed world transformation
    • +
    • An initial world state
    • +
    +
    +
    main :: IO ()
    +main = yMainLoop inputActionMap idleAction initialWorld
    +
    + +

    Here is our initial world state.

    +
    +
    -- We initialize the world state
    +-- then angle, position and zoom of the camera
    +-- And the shape function
    +initialWorld :: World
    +initialWorld = World {
    +   angle = makePoint3D (-30,-30,0)
    + , position = makePoint3D (0,0,0)
    + , scale = 0.8
    + , shape = shapeFunc 
    + , box = Box3D { minPoint = makePoint3D (-2,-2,-2)
    +               , maxPoint =  makePoint3D (2,2,2)
    +               , resolution =  0.16 }
    + , told = 0
    + }
    +
    + +

    We will define shapeFunc later. Here is the function which transform the world even without user action. Mainly it makes some rotation.

    +
    +
    idleAction :: Time -> World -> World
    +idleAction tnew world = world {
    +    angle = (angle world) + (delta -*< zdir)
    +  , told = tnew
    +  }
    +  where 
    +      anglePerSec = 5.0
    +      delta = anglePerSec * elapsed / 1000.0
    +      elapsed = fromIntegral (tnew - (told world))
    +
    + +

    Now the function which will generate points in 3D. The first parameter (res) is the resolution of the vertex generation. More precisely, res is distance between two points on one direction. We need it to “close” our shape.

    +

    The type Function3D is Point -> Point -> Maybe Point. Because we consider partial functions (for some (x,y) our function can be undefined).

    +
    +
    shapeFunc :: Scalar -> Function3D
    +shapeFunc res x y = 
    +  let 
    +      z = maxZeroIndex (ymandel x y) 0 1 20
    +  in
    +  if and [ maxZeroIndex (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 |
    +              val <- [res], xeps <- [-val,val], yeps<-[-val,val]]
    +      then Nothing 
    +      else Just (z,colorFromValue ((ymandel x y z) * 64))
    +
    + +

    With the color function.

    +
    +
    colorFromValue :: Point -> Color
    +colorFromValue n =
    +  let 
    +      t :: Point -> Scalar
    +      t i = 0.7 + 0.3*cos( i / 10 )
    +  in
    +    makeColor (t n) (t (n+5)) (t (n+10))
    +
    + +

    The rest is similar to the preceding sections.

    +
    +
    -- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => 
    +                 (a -> b) -> a -> a -> Int -> a
    +maxZeroIndex _ minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if (func medpoint) /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    +ymandel :: Point -> Point -> Point -> Point
    +ymandel x y z = fromIntegral (mandel x y z 64) / 64
    +
    + +

    I won’t explain how the magic occurs here. If you are interested, just read the file YGL.hs. It is commented a lot.

    + +

    Download the source code of this section → 05_Mandelbulb/Mandelbulb.lhs

    +
    +

    Download the source code of this section → 06_Mandelbulb/Mandelbulb.lhs

    +

    Optimization

    +

    Our code architecture feel very clean. All the meaningful code is in our main file and all display details are externalized. If you read the code of YGL.hs, you’ll see I didn’t made everything perfect. For example, I didn’t finished the code of the lights. But I believe it is a good first step and it will be easy to go further. Unfortunately the program of the preceding session is extremely slow. We compute the Mandelbulb for each frame now.

    +

    Before our program structure was:

    +
    Constant Function -> Constant List of Triangles -> Display
    +

    Now we have

    +
    Main loop -> World -> Function -> List of Objects -> Atoms -> Display
    +

    The World state could change. The compiler can no more optimize the computation for us. We have to manually explain when to redraw the shape.

    +

    To optimize we must do some things in a lower level. Mostly the program remains the same, but it will provide the list of atoms directly.

    +
    + +
    +
    import YGL -- Most the OpenGL Boilerplate
    +import Mandel -- The 3D Mandelbrot maths
    +
    +-- Centralize all user input interaction
    +inputActionMap :: InputMap World
    +inputActionMap = inputMapFromList [
    +     (Press ' ' , switchRotation)
    +    ,(Press 'k' , rotate xdir 5)
    +    ,(Press 'i' , rotate xdir (-5))
    +    ,(Press 'j' , rotate ydir 5)
    +    ,(Press 'l' , rotate ydir (-5))
    +    ,(Press 'o' , rotate zdir 5)
    +    ,(Press 'u' , rotate zdir (-5))
    +    ,(Press 'f' , translate xdir 0.1)
    +    ,(Press 's' , translate xdir (-0.1))
    +    ,(Press 'e' , translate ydir 0.1)
    +    ,(Press 'd' , translate ydir (-0.1))
    +    ,(Press 'z' , translate zdir 0.1)
    +    ,(Press 'r' , translate zdir (-0.1))
    +    ,(Press '+' , zoom 1.1)
    +    ,(Press '-' , zoom (1/1.1))
    +    ,(Press 'h' , resize 2.0)
    +    ,(Press 'g' , resize (1/2.0))
    +    ]
    +
    + +
    + +
    +
    data World = World {
    +      angle       :: Point3D
    +    , anglePerSec :: Scalar
    +    , scale       :: Scalar
    +    , position    :: Point3D
    +    , box         :: Box3D
    +    , told        :: Time 
    +    -- We replace shape by cache
    +    , cache       :: [YObject]
    +    } 
    +
    + +
    +
    instance DisplayableWorld World where
    +  winTitle _ = "The YGL Mandelbulb"
    +  camera w = Camera {
    +        camPos = position w, 
    +        camDir = angle w,
    +        camZoom = scale w }
    +  -- We update our objects instanciation
    +  objects = cache
    +
    + +
    + +
    +
    xdir :: Point3D
    +xdir = makePoint3D (1,0,0)
    +ydir :: Point3D
    +ydir = makePoint3D (0,1,0)
    +zdir :: Point3D
    +zdir = makePoint3D (0,0,1)
    +
    +rotate :: Point3D -> Scalar -> World -> World
    +rotate dir angleValue world = 
    +  world {
    +     angle = angle world + (angleValue -*< dir) }
    +
    +switchRotation :: World -> World
    +switchRotation world = 
    +  world {
    +     anglePerSec = if anglePerSec world > 0 then 0 else 5.0 }
    +
    +translate :: Point3D -> Scalar -> World -> World
    +translate dir len world = 
    +  world {
    +    position = position world + (len -*< dir) }
    +
    +zoom :: Scalar -> World -> World
    +zoom z world = world {
    +    scale = z * scale world }
    +
    + +
    +
    main :: IO ()
    +main = yMainLoop inputActionMap idleAction initialWorld
    +
    + +
    + +

    Our initial world state is slightly changed:

    +
    +
    -- We initialize the world state
    +-- then angle, position and zoom of the camera
    +-- And the shape function
    +initialWorld :: World
    +initialWorld = World {
    +   angle = makePoint3D (30,30,0)
    + , anglePerSec = 5.0
    + , position = makePoint3D (0,0,0)
    + , scale = 1.0
    + , box = Box3D { minPoint = makePoint3D (0-eps, 0-eps, 0-eps)
    +               , maxPoint = makePoint3D (0+eps, 0+eps, 0+eps)
    +               , resolution =  0.02 }
    + , told = 0
    + -- We declare cache directly this time
    + , cache = objectFunctionFromWorld initialWorld
    + }
    + where eps=2
    +
    + +

    The use of eps is a hint to make a better zoom by computing with the right bounds.

    +

    We use the YGL.getObject3DFromShapeFunction function directly. This way instead of providing XYFunc, we provide directly a list of Atoms.

    +
    +
    objectFunctionFromWorld :: World -> [YObject]
    +objectFunctionFromWorld w = [Atoms atomList]
    +  where atomListPositive = 
    +          getObject3DFromShapeFunction
    +              (shapeFunc (resolution (box w))) (box w)
    +        atomList = atomListPositive ++ 
    +          map negativeTriangle atomListPositive
    +        negativeTriangle (ColoredTriangle (p1,p2,p3,c)) = 
    +              ColoredTriangle (negz p1,negz p3,negz p2,c)
    +              where negz (P (x,y,z)) = P (x,y,-z)
    +
    + +

    We know that resize is the only world change that necessitate to recompute the list of atoms (triangles). Then we update our world state accordingly.

    +
    +
    resize :: Scalar -> World -> World
    +resize r world = 
    +  tmpWorld { cache = objectFunctionFromWorld tmpWorld }
    +  where 
    +      tmpWorld = world { box = (box world) {
    +              resolution = sqrt ((resolution (box world))**2 * r) }}
    +
    + +

    All the rest is exactly the same.

    +
    + +
    +
    idleAction :: Time -> World -> World
    +idleAction tnew world = 
    +      world {
    +        angle = angle world + (delta -*< zdir)
    +      , told = tnew
    +      }
    +  where 
    +      delta = anglePerSec world * elapsed / 1000.0
    +      elapsed = fromIntegral (tnew - (told world))
    +
    +shapeFunc :: Scalar -> Function3D
    +shapeFunc res x y = 
    +  let 
    +      z = maxZeroIndex (ymandel x y) 0 1 20
    +  in
    +  if and [ maxZeroIndex (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 |
    +              val <- [res], xeps <- [-val,val], yeps<-[-val,val]]
    +      then Nothing 
    +      else Just (z,colorFromValue 0)
    +
    +colorFromValue :: Point -> Color
    +colorFromValue n =
    +  let 
    +      t :: Point -> Scalar
    +      t i = 0.0 + 0.5*cos( i /10 )
    +  in
    +    makeColor (t n) (t (n+5)) (t (n+10))
    +
    +-- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => 
    +                 (a -> b) -> a -> a -> Int -> a
    +maxZeroIndex _ minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if func medpoint /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    +ymandel :: Point -> Point -> Point -> Point
    +ymandel x y z = fromIntegral (mandel x y z 64) / 64
    +
    + +
    + +

    And you can also consider minor changes in the YGL.hs source file.

    + +

    Download the source code of this section → 06_Mandelbulb/Mandelbulb.lhs

    +

    Conclusion

    +

    As we can use imperative style in a functional language, know you can use functional style in imperative languages. This article exposed a way to organize some code in a functional way. I’d like to stress the usage of Haskell made it very simple to achieve this.

    +

    Once you are used to pure functional style, it is hard not to see all advantages it offers.

    +

    The code in the two last sections is completely pure and functional. Furthermore I don’t use GLfloat, Color3 or any other OpenGL type. If I want to use another library in the future, I would be able to keep all the pure code and simply update the YGL module.

    +

    The YGL module can be seen as a “wrapper” around 3D display and user interaction. It is a clean separator between the imperative paradigm and functional paradigm.

    +

    If you want to go further, it shouldn’t be hard to add parallelism. This should be easy mainly because most of the visible code is pure. Such an optimization would have been harder by using directly the OpenGL library.

    +

    You should also want to make a more precise object. Because, the Mandelbulb is clearly not convex. But a precise rendering might be very long from O(n².log(n)) to O(n³).

    +
    +
    +
      +
    1. Unfortunately, I couldn’t make this program to work on my Mac. More precisely, I couldn’t make the DevIL library work on Mac to output the image. Yes I have done a brew install libdevil. But even a minimal program who simply write some jpg didn’t worked. I tried both with Haskell and C.

    2. +
    3. Generally in Haskell you need to declare a lot of import lines. This is something I find annoying. In particular, it should be possible to create a special file, Import.hs which make all the necessary import for you, as you generally need them all. I understand why this is cleaner to force the programmer not to do so, but, each time I do a copy/paste, I feel something is wrong. I believe this concern can be generalized to the lack of namespace in Haskell.

    4. +
    5. I tried Complex Double, Complex Float, this current data type with Double and the actual version Float. For rendering a 1024x1024 Mandelbrot set it takes Complex Double about 6.8s, for Complex Float about 5.1s, for the actual version with Double and Float it takes about 1.6 sec. See these sources for testing yourself: https://gist.github.com/2945043. If you really want to things to go faster, use data Complex = C {-# UNPACK #-} !Float {-# UNPACK #-} !Float. It takes only one second instead of 1.6s.

    6. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2012-06-15 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/Haskell-the-Hard-Way/index.html b/Scratch/fr/blog/Haskell-the-Hard-Way/index.html new file mode 100644 index 0000000..d87bce3 --- /dev/null +++ b/Scratch/fr/blog/Haskell-the-Hard-Way/index.html @@ -0,0 +1,2360 @@ + + + + + + YBlog - Haskell comme un vrai! + + + + + + + + + + + + + +
    + + +
    +

    Haskell comme un vrai!

    +
    +
    +
    +
    +

    Magritte pleasure principle

    + +
    + +

    Je pense vraiment que tous les développeurs devraient apprendre Haskell. Peut-être pas devenir des ninjas d’Haskell, mais au moins savoir ce que ce langage a de particulier. Son apprentissage ouvre énormément l’esprit.

    +

    La plupart des langages partagent les mêmes fondamentaux :

    +
      +
    • les variables
    • +
    • les boucles
    • +
    • les pointeurs1
    • +
    • les structures de données, les objets et les classes
    • +
    +

    Haskell est très différent. Ce langage utilise des concepts dont je n’avais jamais entendu parlé avant. Beaucoup de ces concepts pourront vous aider à devenir un meilleur développeur.

    +

    Plier son esprit à Haskell peut être difficile. Ce le fût pour moi. Dans cet article, j’essaye de fournir les informations qui m’ont manquées lors de mon apprentissage.

    +

    Cet article sera certainement difficile à suivre. Mais c’est voulu. Il n’y a pas de raccourci pour apprendre Haskell. C’est difficile. Mais je pense que c’est une bonne chose. C’est parce qu’Haskell est difficile qu’il est intéressant.

    +

    La manière conventionnelle d’apprendre Haskell est de lire deux livres. En premier “Learn You a Haskell” et ensuite “Real World Haskell”. Je pense aussi que c’est la bonne manière de s’y prendre. Mais apprendre même un tout petit peu d’Haskell est presque impossible sans se plonger réellement dans ces livres.

    +

    Cet article fait un résumé très dense et rapide des aspect majeurs d’Haskell. J’y ai aussi rajouté des informations qui m’ont manqué pendant l’apprentissage de ce langage.

    +

    Pour les francophones ; je suis désolé. Je n’ai pas eu le courage de tout retraduire en français. Sachez cependant que si vous êtes plusieurs à insister, je ferai certainement l’effort de traduire l’article en entier. Et si vous vous sentez d’avoir une bonne âme je ne suis pas contre un peu d’aide. Les sources de cet article sont sur gihub.

    +

    Cet article contient cinq parties :

    +
      +
    • Introduction : un exemple rapide pour montrer qu’Haskell peut être facile.
    • +
    • Les bases d’Haskell : La syntaxe et des notions essentielles
    • +
    • Partie difficile : +
        +
      • Style fonctionnel : un exemple progressif, du style impératif au style fonctionnel ;
      • +
      • Types : la syntaxe et un exemple d’arbre binaire ;
      • +
      • Structure infinie : manipulons un arbre infini !
      • +
    • +
    • Partie de difficulté infernale : +
        +
      • Utiliser les IO : un exemple très minimal ;
      • +
      • Le truc des IO révélé : les détails cachés d’IO qui m’ont manqués
      • +
      • Les monades : incroyable à quel point on peut généraliser
      • +
    • +
    • Appendice : +
        +
      • Revenons sur les arbres infinis : une discussion plus mathématique sur la manipulation d’arbres infinis.
      • +
    • +
    +
    +Note: Chaque fois que vous voyez un séparateur avec un nom de fichier se terminant par lhs, vous pouvez cliquer sur le nom de fichier et télécharger le fichier. Si vous sauvegardez le fichier sour le nom filename.lhs, vous pouvez l’exécuter avec : +
    +runhaskell filename.lhs
    +
    + +

    Certains ne marcheront pas, mais la majorité vous donneront un résultat. Vous devriez voir un lien juste en dessous.

    +
    +
    + +
    +

    01_basic/10_Introduction/00_hello_world.lhs

    +

    +Introduction +

    + +

    +Install +

    + +

    + +

    Tools:

    +
      +
    • ghc: Compiler similar to gcc for C.
    • +
    • ghci: Interactive Haskell (REPL)
    • +
    • runhaskell: Execute a program without compiling it. Convenient but very slow compared to compiled programs.
    • +
    +

    +Don’t be afraid +

    + +

    The Scream

    +

    Many book/articles about Haskell start by introducing some esoteric formula (quick sort, Fibonacci, etc…). I will do the exact opposite. At first I won’t show you any Haskell super power. I will start with similarities between Haskell and other programming languages. Let’s jump to the mandatory “Hello World”.

    +
    +
    main = putStrLn "Hello World!"
    +
    +

    To run it, you can save this code in a hello.hs and:

    +
    ~ runhaskell ./hello.hs
    +Hello World!
    +

    You could also download the literate Haskell source. You should see a link just above the introduction title. Download this file as 00_hello_world.lhs and:

    +
    ~ runhaskell 00_hello_world.lhs
    +Hello World!
    +

    01_basic/10_Introduction/00_hello_world.lhs

    +
    +

    01_basic/10_Introduction/10_hello_you.lhs

    +

    Now, a program asking your name and replying “Hello” using the name you entered:

    +
    +
    main = do
    +    print "What is your name?"
    +    name <- getLine
    +    print ("Hello " ++ name ++ "!")
    +
    +

    First, let us compare with a similar program in some imperative languages:

    +
    # Python
    +print "What is your name?"
    +name = raw_input()
    +print "Hello %s!" % name
    +
    # Ruby
    +puts "What is your name?"
    +name = gets.chomp
    +puts "Hello #{name}!"
    +
    // In C
    +#include <stdio.h>
    +int main (int argc, char **argv) {
    +    char name[666]; // <- An Evil Number!
    +    // What if my name is more than 665 character long?
    +    printf("What is your name?\n"); 
    +    scanf("%s", name);
    +    printf("Hello %s!\n", name);
    +    return 0;
    +}
    +

    The structure is the same, but there are some syntax differences. A major part of this tutorial will be dedicated to explaining why.

    +

    In Haskell, there is a main function and every object has a type. The type of main is IO (). This means, main will cause side effects.

    +

    Just remember that Haskell can look a lot like mainstream imperative languages.

    +

    01_basic/10_Introduction/10_hello_you.lhs

    +
    +

    01_basic/10_Introduction/20_very_basic.lhs

    +

    +Very basic Haskell +

    + +

    Picasso minimal owl

    +

    Before continuing you need to be warned about some essential properties of Haskell.

    +

    Functional

    +

    Haskell is a functional language. If you have an imperative language background, you’ll have to learn a lot of new things. Hopefully many of these new concepts will help you to program even in imperative languages.

    +

    Smart Static Typing

    +

    Instead of being in your way like in C, C++ or Java, the type system is here to help you.

    +

    Purity

    +

    Generally your functions won’t modify anything in the outside world. This means, it can’t modify the value of a variable, can’t get user input, can’t write on the screen, can’t launch a missile. On the other hand, parallelism will be very easy to achieve. Haskell makes it clear where effects occur and where you are pure. Also, it will be far easier to reason about your program. Most bugs will be prevented in the pure parts of your program.

    +

    Furthermore pure functions follow a fundamental law in Haskell:

    +
    +

    Applying a function with the same parameters always returns the same value.

    +
    +

    Laziness

    +

    Laziness by default is a very uncommon language design. By default, Haskell evaluates something only when it is needed. In consequence, it provides a very elegant way to manipulate infinite structures for example.

    +

    A last warning on how you should read Haskell code. For me, it is like reading scientific papers. Some parts are very clear, but when you see a formula, just focus and read slower. Also, while learning Haskell, it really doesn’t matter much if you don’t understand syntax details. If you meet a >>=, <$>, <- or any other weird symbol, just ignore them and follows the flow of the code.

    +

    +Function declaration +

    + +

    You might be used to declare functions like this:

    +

    In C:

    +
    int f(int x, int y) {
    +    return x*x + y*y;
    +}
    +

    In Javascript:

    +
    function f(x,y) {
    +    return x*x + y*y;
    +}
    +

    in Python:

    +
    def f(x,y):
    +    return x*x + y*y
    +

    in Ruby:

    +
    def f(x,y)
    +    x*x + y*y
    +end
    +

    In Scheme:

    +
    (define (f x y)
    +    (+ (* x x) (* y y)))
    +

    Finally, the Haskell way is:

    +
    f x y = x*x + y*y
    +

    Very clean. No parenthesis, no def.

    +

    Don’t forget, Haskell uses functions and types a lot. It is thus very easy to define them. The syntax was particularly well thought for these objects.

    +

    +A Type Example +

    + +

    The usual way is to declare the type of your function. This is not mandatory. The compiler is smart enough to discover it for you.

    +

    Let’s play a little.

    +
    +
    -- We declare the type using ::
    +f :: Int -> Int -> Int
    +f x y = x*x + y*y
    +
    +main = print (f 2 3)
    +
    +
    ~ runhaskell 20_very_basic.lhs
    +13
    +

    01_basic/10_Introduction/20_very_basic.lhs

    +
    +

    01_basic/10_Introduction/21_very_basic.lhs

    +

    Now try

    +
    +
    f :: Int -> Int -> Int
    +f x y = x*x + y*y
    +
    +main = print (f 2.3 4.2)
    +
    +

    You get this error:

    +
    21_very_basic.lhs:6:23:
    +    No instance for (Fractional Int)
    +      arising from the literal `4.2'
    +    Possible fix: add an instance declaration for (Fractional Int)
    +    In the second argument of `f', namely `4.2'
    +    In the first argument of `print', namely `(f 2.3 4.2)'
    +    In the expression: print (f 2.3 4.2)
    +

    The problem: 4.2 isn’t an Int.

    +

    01_basic/10_Introduction/21_very_basic.lhs

    +
    +

    01_basic/10_Introduction/22_very_basic.lhs

    +

    The solution, don’t declare the type for f. Haskell will infer the most general type for us:

    +
    +
    f x y = x*x + y*y
    +
    +main = print (f 2.3 4.2)
    +
    +

    It works! Great, we don’t have to declare a new function for every single type. For example, in C, you’ll have to declare a function for int, for float, for long, for double, etc…

    +

    But, what type should we declare? To discover the type Haskell has found for us, just launch ghci:

    +
    
    +% ghci
    +GHCi, version 7.0.4: http://www.haskell.org/ghc/  :? for help
    +Loading package ghc-prim ... linking ... done.
    +Loading package integer-gmp ... linking ... done.
    +Loading package base ... linking ... done.
    +Loading package ffi-1.0 ... linking ... done.
    +Prelude> let f x y = x*x + y*y
    +Prelude> :type f
    +f :: Num a => a -> a -> a
    +
    + +

    Uh? What is this strange type?

    +
    Num a => a -> a -> a
    +

    First, let’s focus on the right part a -> a -> a. To understand it, just look at a list of progressive examples:

    +

    The written type | Its meaning |
    Int | the type Int |
    Int -> Int | the type function from Int to Int |
    Float -> Int | the type function from Float to Int |
    a -> Int | the type function from any type to Int |
    a -> a | the type function from any type a to the same type a |
    a -> a -> a | the type function of two arguments of any type a to the same type a |

    +

    In the type a -> a -> a, the letter a is a type variable. It means f is a function with two arguments and both arguments and the result have the same type. The type variable a could take many different type value. For example Int, Integer, Float

    +

    So instead of having a forced type like in C with declaring the function for int, long, float, double, etc… We declare only one function like in a dynamically typed language.

    +

    Generally a can be any type. For example a String, an Int, but also more complex types, like Trees, other functions, etc… But here our type is prefixed with Num a =>.

    +

    Num is a type class. A type class can be understood as a set of types. Num contains only types which behave like numbers. More precisely, Num is class containing types who implement a specific list of functions, and in particular (+) and (*).

    +

    Type classes are a very powerful language construct. We can do some incredibly powerful stuff with this. More on this later.

    +

    Finally, Num a => a -> a -> a means:

    +

    Let a be a type belonging to the Num type class. This is a function from type a to (a -> a).

    +

    Yes, strange. In fact, in Haskell no function really has two arguments. Instead all functions have only one argument. But we will note that taking two arguments is equivalent to taking one argument and returning a function taking the second argument as parameter.

    +

    More precisely f 3 4 is equivalent to (f 3) 4. Note f 3 is a function:

    +
    f :: Num a :: a -> a -> a
    +
    +g :: Num a :: a -> a
    +g = f 3
    +
    +g y ⇔ 3*3 + y*y
    +

    Another notation exists for functions. The lambda notation allows us to create functions without assigning them a name. We call them anonymous function. We could have written:

    +
    g = \y -> 3*3 + y*y
    +

    The \ is used because it looks like λ and is ASCII.

    +

    If you are not used to functional programming your brain should start to heat up. It is time to make a real application.

    +

    01_basic/10_Introduction/22_very_basic.lhs

    +
    +

    01_basic/10_Introduction/23_very_basic.lhs

    +

    But just before that, we should verify the type system works as expected:

    +
    +
    f :: Num a => a -> a -> a
    +f x y = x*x + y*y
    +
    +main = print (f 3 2.4)
    +
    +

    It works, because, 3 is a valid representation both for Fractional numbers like Float and for Integer. As 2.4 is a Fractional number, 3 is then interpreted as being also a Fractional number.

    +

    01_basic/10_Introduction/23_very_basic.lhs

    +
    +

    01_basic/10_Introduction/24_very_basic.lhs

    +

    If we force our function to work with different types, it will fail:

    +
    +
    f :: Num a => a -> a -> a
    +f x y = x*x + y*y
    +
    +x :: Int
    +x = 3
    +y :: Float
    +y = 2.4
    +main = print (f x y) -- won't work because type x ≠ type y
    +
    +

    The compiler complains. The two parameters must have the same type.

    +

    If you believe it is a bad idea, and the compiler should make the transformation from a type to another for you, you should really watch this great (and funny) video: WAT

    +

    01_basic/10_Introduction/24_very_basic.lhs

    +

    +Essential Haskell +

    + +

    Kandinsky Gugg

    +

    I suggest you to skim this part. Think of it like a reference. Haskell has a lot of features. Many informations are missing here. Get back here if notation feels strange.

    +

    I use the symbol to state that two expression are equivalent. It is a meta notation, does not exists in Haskell. I will also use to show what is the return of an expression.

    +

    +Notations +

    + +
    +Arithmetic +
    + +
    3 + 2 * 6 / 3 ⇔ 3 + ((2*6)/3)
    +
    +Logic +
    + +
    True || False ⇒ True
    +True && False ⇒ False
    +True == False ⇒ False
    +True /= False ⇒ True  (/=) is the operator for different
    +
    +Powers +
    + +
    x^n     for n an integral (understand Int or Integer)
    +x**y    for y any kind of number (Float for example)
    +

    Integer have no limit except the capacity of your machine:

    +
    4^103
    +102844034832575377634685573909834406561420991602098741459288064
    +

    Yeah! And also rational numbers FTW! But you need to import the module Data.Ratio:

    +
    $ ghci
    +....
    +Prelude> :m Data.Ratio
    +Data.Ratio> (11 % 15) * (5 % 3)
    +11 % 9
    +
    +Lists +
    + +
    []                      ⇔ empty list
    +[1,2,3]                 ⇔ List of integral
    +["foo","bar","baz"]     ⇔ List of String
    +1:[2,3]                 ⇔ [1,2,3], (:) prepend one element
    +1:2:[]                  ⇔ [1,2]
    +[1,2] ++ [3,4]          ⇔ [1,2,3,4], (++) concatenate
    +[1,2,3] ++ ["foo"]      ⇔ ERROR String ≠ Integral
    +[1..4]                  ⇔ [1,2,3,4]
    +[1,3..10]               ⇔ [1,3,5,7,9]
    +[2,3,5,7,11..100]       ⇔ ERROR! I am not so smart!
    +[10,9..1]               ⇔ [10,9,8,7,6,5,4,3,2,1]
    +
    +Strings +
    + +

    In Haskell strings are list of Char.

    +
    'a' :: Char
    +"a" :: [Char]
    +""  ⇔ []
    +"ab" ⇔ ['a','b'] ⇔  'a':"b" ⇔ 'a':['b'] ⇔ 'a':'b':[]
    +"abc" ⇔ "ab"++"c"
    +
    +

    Remark: In real code you shouldn’t use list of char to represent text. You should mostly use Data.Text instead. If you want to represent stream of ASCII char, you should use Data.ByteString.

    +
    +
    +Tuples +
    + +

    The type of couple is (a,b). Elements in a tuple can have different type.

    +
    -- All these tuple are valid
    +(2,"foo")
    +(3,'a',[2,3])
    +((2,"a"),"c",3)
    +
    +fst (x,y)       ⇒  x
    +snd (x,y)       ⇒  y
    +
    +fst (x,y,z)     ⇒  ERROR: fst :: (a,b) -> a
    +snd (x,y,z)     ⇒  ERROR: snd :: (a,b) -> b
    +
    +Deal with parentheses +
    + +

    To remove some parentheses you can use two functions: ($) and (.).

    +
    -- By default:
    +f g h x         ⇔  (((f g) h) x)
    +
    +-- the $ replace parenthesis from the $
    +-- to the end of the expression
    +f g $ h x       ⇔  f g (h x) ⇔ (f g) (h x)
    +f $ g h x       ⇔  f (g h x) ⇔ f ((g h) x)
    +f $ g $ h x     ⇔  f (g (h x))
    +
    +-- (.) the composition function
    +(f . g) x       ⇔  f (g x)
    +(f . g . h) x   ⇔  f (g (h x))
    +
    +

    01_basic/20_Essential_Haskell/10a_Functions.lhs

    +

    +Useful notations for functions +

    + +

    Just a reminder:

    +
    x :: Int            ⇔ x is of type Int
    +x :: a              ⇔ x can be of any type
    +x :: Num a => a     ⇔ x can be any type a
    +                      such that a belongs to Num type class 
    +f :: a -> b         ⇔ f is a function from a to b
    +f :: a -> b -> c    ⇔ f is a function from a to (b→c)
    +f :: (a -> b) -> c  ⇔ f is a function from (a→b) to c
    +

    Defining the type of a function before its declaration isn’t mandatory. Haskell infers the most general type for you. But it is considered a good practice to do so.

    +

    Infix notation

    +
    +
    square :: Num a => a -> a  
    +square x = x^2
    +
    +

    Note ^ use infix notation. For each infix operator there its associated prefix notation. You just have to put it inside parenthesis.

    +
    +
    square' x = (^) x 2
    +
    +square'' x = (^2) x
    +
    +

    We can remove x in the left and right side! It’s called η-reduction.

    +
    +
    square''' = (^2)
    +
    +

    Note we can declare function with ' in their name. Here:

    +
    +

    squaresquare'square''square '''

    +
    +

    Tests

    +

    An implementation of the absolute function.

    +
    +
    absolute :: (Ord a, Num a) => a -> a
    +absolute x = if x >= 0 then x else -x
    +
    +

    Note: the if .. then .. else Haskell notation is more like the ¤?¤:¤ C operator. You cannot forget the else.

    +

    Another equivalent version:

    +
    +
    absolute' x
    +    | x >= 0 = x
    +    | otherwise = -x
    +
    + +
    +

    Notation warning: indentation is important in Haskell. Like in Python, a bad indentation could break your code!

    +
    +
    + +
    +
    main = do
    +      print $ square 10
    +      print $ square' 10
    +      print $ square'' 10
    +      print $ square''' 10
    +      print $ absolute 10
    +      print $ absolute (-10)
    +      print $ absolute' 10
    +      print $ absolute' (-10)
    +
    +
    + +

    01_basic/20_Essential_Haskell/10a_Functions.lhs

    +

    +Hard Part +

    + +

    The hard part can now begin.

    +

    +Functional style +

    + +

    Biomechanical Landscape by H.R. Giger

    +

    In this section, I will give a short example of the impressive refactoring ability provided by Haskell. We will select a problem and solve it using a standard imperative way. Then I will make the code evolve. The end result will be both more elegant and easier to adapt.

    +

    Let’s solve the following problem:

    +
    +

    Given a list of integers, return the sum of the even numbers in the list.

    +

    example: [1,2,3,4,5] ⇒ 2 + 4 ⇒ 6

    +
    +

    To show differences between the functional and imperative approach, I’ll start by providing an imperative solution (in Javascript):

    +
    function evenSum(list) {
    +    var result = 0;
    +    for (var i=0; i< list.length ; i++) {
    +        if (list[i] % 2 ==0) {
    +            result += list[i];
    +        }
    +    }
    +    return result;
    +}
    +

    But, in Haskell we don’t have variables, nor for loop. One solution to achieve the same result without loops is to use recursion.

    +
    +

    Remark: Recursion is generally perceived as slow in imperative languages. But it is generally not the case in functional programming. Most of the time Haskell will handle recursive functions efficiently.

    +
    +

    Here is a C version of the recursive function. Note that for simplicity, I assume the int list ends with the first 0 value.

    +
    int evenSum(int *list) {
    +    return accumSum(0,list);
    +}
    +
    +int accumSum(int n, int *list) {
    +    int x;
    +    int *xs;
    +    if (*list == 0) { // if the list is empty
    +        return n;
    +    } else {
    +        x = list[0]; // let x be the first element of the list
    +        xs = list+1; // let xs be the list without x
    +        if ( 0 == (x%2) ) { // if x is even
    +            return accumSum(n+x, xs);
    +        } else {
    +            return accumSum(n, xs);
    +        }
    +    }
    +}
    +

    Keep this code in mind. We will translate it into Haskell. But before, I need to introduce three simple but useful functions we will use:

    +
    even :: Integral a => a -> Bool
    +head :: [a] -> a
    +tail :: [a] -> [a]
    +

    even verifies if a number is even.

    +
    even :: Integral a => a -> Bool
    +even 3   False
    +even 2   True
    +

    head returns the first element of a list:

    +
    head :: [a] -> a
    +head [1,2,3]  1
    +head []       ERROR
    +

    tail returns all elements of a list, except the first:

    +
    tail :: [a] -> [a]
    +tail [1,2,3]  [2,3]
    +tail [3]      []
    +tail []       ERROR
    +

    Note that for any non empty list l, l ⇔ (head l):(tail l)

    +
    +

    02_Hard_Part/11_Functions.lhs

    +

    The first Haskell solution. The function evenSum returns the sum of all even numbers in a list:

    +
    +
    -- Version 1
    +evenSum :: [Integer] -> Integer
    +
    +evenSum l = accumSum 0 l
    +
    +accumSum n l = if l == []
    +                  then n
    +                  else let x = head l 
    +                           xs = tail l 
    +                       in if even x
    +                              then accumSum (n+x) xs
    +                              else accumSum n xs
    +
    +

    To test a function you can use ghci:

    +
    +% ghci
    +GHCi, version 7.0.3: http://www.haskell.org/ghc/  :? for help
    +Loading package ghc-prim ... linking ... done.
    +Loading package integer-gmp ... linking ... done.
    +Loading package base ... linking ... done.
    +Prelude> :load 11_Functions.lhs 
    +[1 of 1] Compiling Main             ( 11_Functions.lhs, interpreted )
    +Ok, modules loaded: Main.
    +*Main> evenSum [1..5]
    +6
    +
    + +

    Here is an example of execution2:

    +
    +*Main> evenSum [1..5]
    +accumSum 0 [1,2,3,4,5]
    +1 is odd
    +accumSum 0 [2,3,4,5]
    +2 is even
    +accumSum (0+2) [3,4,5]
    +3 is odd
    +accumSum (0+2) [4,5]
    +4 is even
    +accumSum (0+2+4) [5]
    +5 is odd
    +accumSum (0+2+4) []
    +l == []
    +0+2+4
    +0+6
    +6
    +
    + +

    Coming from an imperative language all should seem right. In reality many things can be improved. First, we can generalize the type.

    +
    evenSum :: Integral a => [a] -> a
    +
    + +
    +
    main = do print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/11_Functions.lhs

    +
    +

    02_Hard_Part/12_Functions.lhs

    +

    Next, we can use sub functions using where or let. This way our accumSum function won’t pollute the global namespace.

    +
    +
    -- Version 2
    +evenSum :: Integral a => [a] -> a
    +
    +evenSum l = accumSum 0 l
    +    where accumSum n l = 
    +            if l == []
    +                then n
    +                else let x = head l 
    +                         xs = tail l 
    +                     in if even x
    +                            then accumSum (n+x) xs
    +                            else accumSum n xs
    +
    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/12_Functions.lhs

    +
    +

    02_Hard_Part/13_Functions.lhs

    +

    Next, we can use pattern matching.

    +
    +
    -- Version 3
    +evenSum l = accumSum 0 l
    +    where 
    +        accumSum n [] = n
    +        accumSum n (x:xs) = 
    +             if even x
    +                then accumSum (n+x) xs
    +                else accumSum n xs
    +
    +

    What is pattern matching? Use values instead of general parameter names3.

    +

    Instead of saying: foo l = if l == [] then <x> else <y> You simply state:

    +
    foo [] =  <x>
    +foo l  =  <y>
    +

    But pattern matching goes even further. It is also able to inspect the inner data of a complex value. We can replace

    +
    foo l =  let x  = head l 
    +             xs = tail l
    +         in if even x 
    +             then foo (n+x) xs
    +             else foo n xs
    +

    with

    +
    foo (x:xs) = if even x 
    +                 then foo (n+x) xs
    +                 else foo n xs
    +

    This is a very useful feature. It makes our code both terser and easier to read.

    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/13_Functions.lhs

    +
    +

    02_Hard_Part/14_Functions.lhs

    +

    In Haskell you can simplify function definition by η-reducing them. For example, instead of writing:

    +
    f x = (some expresion) x
    +

    you can simply write

    +
    f = some expression
    +

    We use this method to remove the l:

    +
    +
    -- Version 4
    +evenSum :: Integral a => [a] -> a
    +
    +evenSum = accumSum 0
    +    where 
    +        accumSum n [] = n
    +        accumSum n (x:xs) = 
    +             if even x
    +                then accumSum (n+x) xs
    +                else accumSum n xs
    +
    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/14_Functions.lhs

    +
    +

    02_Hard_Part/15_Functions.lhs

    +

    +Higher Order Functions +

    + +

    Escher

    +

    To make things even better we should use higher order functions. What are these beasts? Higher order functions are functions taking functions as parameter.

    +

    Here are some examples:

    +
    filter :: (a -> Bool) -> [a] -> [a]
    +map :: (a -> b) -> [a] -> [b]
    +foldl :: (a -> b -> a) -> a -> [b] -> a
    +

    Let’s proceed by small steps.

    +
    -- Version 5
    +evenSum l = mysum 0 (filter even l)
    +    where
    +      mysum n [] = n
    +      mysum n (x:xs) = mysum (n+x) xs
    +

    where

    +
    filter even [1..10] ⇔  [2,4,6,8,10]
    +

    The function filter takes a function of type (a -> Bool) and a list of type [a]. It returns a list containing only elements for which the function returned true.

    +

    Our next step is to use another way to simulate a loop. We will use the foldl function to accumulate a value. The function foldl captures a general coding pattern:

    +
    +myfunc list = foo initialValue list
    +    foo accumulated []     = accumulated
    +    foo tmpValue    (x:xs) = foo (bar tmpValue x) xs
    +
    + +

    Which can be replaced by:

    +
    +myfunc list = foldl bar initialValue list
    +
    + +

    If you really want to know how the magic works. Here is the definition of foldl.

    +
    foldl f z [] = z
    +foldl f z (x:xs) = foldl f (f z x) xs
    +
    foldl f z [x1,...xn]
    +⇔  f (... (f (f z x1) x2) ...) xn
    +

    But as Haskell is lazy, it doesn’t evaluate (f z x) and pushes it to the stack. This is why we generally use foldl' instead of foldl; foldl' is a strict version of foldl. If you don’t understand what lazy and strict means, don’t worry, just follow the code as if foldl and foldl' where identical.

    +

    Now our new version of evenSum becomes:

    +
    -- Version 6
    +-- foldl' isn't accessible by default
    +-- we need to import it from the module Data.List
    +import Data.List
    +evenSum l = foldl' mysum 0 (filter even l)
    +  where mysum acc value = acc + value
    +

    Version we can simplify by using directly a lambda notation. This way we don’t have to create the temporary name mysum.

    +
    +
    -- Version 7
    +-- Generally it is considered a good practice
    +-- to import only the necessary function(s)
    +import Data.List (foldl')
    +evenSum l = foldl' (\x y -> x+y) 0 (filter even l)
    +
    +

    And of course, we note that

    +
    (\x y -> x+y) ⇔ (+)
    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/15_Functions.lhs

    +
    +

    02_Hard_Part/16_Functions.lhs

    +

    Finally

    +
    -- Version 8
    +import Data.List (foldl')
    +evenSum :: Integral a => [a] -> a
    +evenSum l = foldl' (+) 0 (filter even l)
    +

    foldl' isn’t the easiest function to intuit. If you are not used to it, you should study it a bit.

    +

    To help you understand what’s going on here, a step by step evaluation:

    +
    +  evenSum [1,2,3,4]
    +⇒ foldl' (+) 0 (filter even [1,2,3,4])
    +⇒ foldl' (+) 0 [2,4]
    +⇒ foldl' (+) (0+2) [4] 
    +⇒ foldl' (+) 2 [4]
    +⇒ foldl' (+) (2+4) []
    +⇒ foldl' (+) 6 []
    +⇒ 6
    +
    + +

    Another useful higher order function is (.). The (.) function corresponds to the mathematical composition.

    +
    (f . g . h) x ⇔  f ( g (h x))
    +

    We can take advantage of this operator to η-reduce our function:

    +
    -- Version 9
    +import Data.List (foldl')
    +evenSum :: Integral a => [a] -> a
    +evenSum = (foldl' (+) 0) . (filter even)
    +

    Also, we could rename some parts to make it clearer:

    +
    +
    -- Version 10 
    +import Data.List (foldl')
    +sum' :: (Num a) => [a] -> a
    +sum' = foldl' (+) 0
    +evenSum :: Integral a => [a] -> a
    +evenSum = sum' . (filter even)
    +
    +

    It is time to discuss a bit. What did we gain by using higher order functions?

    +

    At first, you can say it is terseness. But in fact, it has more to do with better thinking. Suppose we want to modify slightly our function. We want to get the sum of all even square of element of the list.

    +
    [1,2,3,4] ▷ [1,4,9,16] ▷ [4,16] ▷ 20
    +

    Update the version 10 is extremely easy:

    +
    +
    squareEvenSum = sum' . (filter even) . (map (^2))
    +squareEvenSum' = evenSum . (map (^2))
    +squareEvenSum'' = sum' . (map (^2)) . (filter even)
    +
    +

    We just had to add another “transformation function”4.

    +
    map (^2) [1,2,3,4] ⇔ [1,4,9,16]
    +

    The map function simply apply a function to all element of a list.

    +

    We didn’t had to modify anything inside the function definition. It feels more modular. But in addition you can think more mathematically about your function. You can then use your function as any other one. You can compose, map, fold, filter using your new function.

    +

    To modify version 1 is left as an exercise to the reader ☺.

    +

    If you believe we reached the end of generalization, then know you are very wrong. For example, there is a way to not only use this function on lists but on any recursive type. If you want to know how, I suggest you to read this quite fun article: Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire by Meijer, Fokkinga and Paterson.

    +

    This example should show you how great pure functional programming is. Unfortunately, using pure functional programming isn’t well suited to all usages. Or at least such a language hasn’t been found yet.

    +

    One of the great powers of Haskell is the ability to create DSLs (Domain Specific Language) making it easy to change the programming paradigm.

    +

    In fact, Haskell is also great when you want to write imperative style programming. Understanding this was really hard for me when learning Haskell. A lot of effort has been done to explain to you how much functional approach is superior. Then when you start the imperative style of Haskell, it is hard to understand why and how.

    +

    But before talking about this Haskell super-power, we must talk about another essential aspect of Haskell: Types.

    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/16_Functions.lhs

    +

    +Types +

    + +

    Dali, the madonna of port Lligat

    +
    +

    tl;dr:

    +
      +
    • type Name = AnotherType is just an alias and the compiler doesn’t do any difference between Name and AnotherType.
    • +
    • data Name = NameConstructor AnotherType make a difference.
    • +
    • data can construct structures which can be recursives.
    • +
    • deriving is magic and create functions for you.
    • +
    +
    +

    In Haskell, types are strong and static.

    +

    Why is this important? It will help you greatly to avoid mistakes. In Haskell, most bugs are caught during the compilation of your program. And the main reason is because of the type inference during compilation. It will be easy to detect where you used the wrong parameter at the wrong place for example.

    +

    +Type inference +

    + +

    Static typing is generally essential to reach fast execution time. But most statically typed languages are bad at generalizing concepts. Haskell’s saving grace is that it can infer types.

    +

    Here is a simple example. The square function in Haskell:

    +
    square x = x * x
    +

    This function can square any Numeral type. You can provide square with an Int, an Integer, a Float a Fractional and even Complex. Proof by example:

    +
    % ghci
    +GHCi, version 7.0.4:
    +...
    +Prelude> let square x = x*x
    +Prelude> square 2
    +4
    +Prelude> square 2.1
    +4.41
    +Prelude> -- load the Data.Complex module
    +Prelude> :m Data.Complex
    +Prelude Data.Complex> square (2 :+ 1)
    +3.0 :+ 4.0
    +

    x :+ y is the notation for the complex (x + ib).

    +

    Now compare with the amount of code necessary in C:

    +
    int     int_square(int x) { return x*x; }
    +
    +float   float_square(float x) {return x*x; }
    +
    +complex complex_square (complex z) {
    +    complex tmp;
    +    tmp.real = z.real * z.real - z.img * z.img;
    +    tmp.img = 2 * z.img * z.real;
    +}
    +
    +complex x,y;
    +y = complex_square(x);
    +

    For each type, you need to write a new function. The only way to work around this problem is to use some meta-programming trick. For example using the pre-processor. In C++ there is a better way, the C++ templates:

    +

    ~~~~~~ {.c++} #include #include using namespace std;

    +

    template T square(T x) { return x*x; }

    +

    int main() { // int int sqr_of_five = square(5); cout << sqr_of_five << endl; // double cout << (double)square(5.3) << endl; // complex cout << square( complex(5,3) ) << endl; return 0; } ~~~~~~

    +

    C++ does a far better job than C. For more complex function the syntax can be hard to follow: look at this article for example.

    +

    In C++ you must declare that a function can work with different types. In Haskell this is the opposite. The function will be as general as possible by default.

    +

    Type inference gives Haskell the feeling of freedom that dynamically typed languages provide. But unlike dynamically typed languages, most errors are caught before the execution. Generally, in Haskell:

    +
    +

    “if it compiles it certainly does what you intended”

    +
    +
    +

    02_Hard_Part/21_Types.lhs

    +

    +Type construction +

    + +

    You can construct your own types. First you can use aliases or type synonyms.

    +
    +
    type Name   = String
    +type Color  = String
    +
    +showInfos :: Name ->  Color -> String
    +showInfos name color =  "Name: " ++ name
    +                        ++ ", Color: " ++ color
    +name :: Name
    +name = "Robin"
    +color :: Color
    +color = "Blue"
    +main = putStrLn $ showInfos name color
    +
    +

    02_Hard_Part/21_Types.lhs

    +
    +

    02_Hard_Part/22_Types.lhs

    +

    But it doesn’t protect you much. Try to swap the two parameter of showInfos and run the program:

    +
        putStrLn $ showInfos color name
    +

    It will compile and execute. In fact you can replace Name, Color and String everywhere. The compiler will treat them as completely identical.

    +

    Another method is to create your own types using the keyword data.

    +
    +
    data Name   = NameConstr String
    +data Color  = ColorConstr String
    +
    +showInfos :: Name ->  Color -> String
    +showInfos (NameConstr name) (ColorConstr color) =
    +      "Name: " ++ name ++ ", Color: " ++ color
    +
    +name  = NameConstr "Robin"
    +color = ColorConstr "Blue"
    +main = putStrLn $ showInfos name color
    +
    +

    Now if you switch parameters of showInfos, the compiler complains! A possible mistake you could never do again. The only price is to be more verbose.

    +

    Also remark constructor are functions:

    +
    NameConstr  :: String -> Name
    +ColorConstr :: String -> Color
    +

    The syntax of data is mainly:

    +
    data TypeName =   ConstructorName  [types]
    +                | ConstructorName2 [types]
    +                | ...
    +

    Generally the usage is to use the same name for the DataTypeName and DataTypeConstructor.

    +

    Example:

    +
    data Complex = Num a => Complex a a
    +

    Also you can use the record syntax:

    +
    data DataTypeName = DataConstructor {
    +                      field1 :: [type of field1]
    +                    , field2 :: [type of field2]
    +                    ...
    +                    , fieldn :: [type of fieldn] }
    +

    And many accessors are made for you. Furthermore you can use another order when setting values.

    +

    Example:

    +
    data Complex = Num a => Complex { real :: a, img :: a}
    +c = Complex 1.0 2.0
    +z = Complex { real = 3, img = 4 }
    +real c  1.0
    +img z  4
    +

    02_Hard_Part/22_Types.lhs

    +
    +

    02_Hard_Part/23_Types.lhs

    +

    +Recursive type +

    + +

    You already encountered a recursive type: lists. You can re-create lists, but with a more verbose syntax:

    +
    data List a = Empty | Cons a (List a)
    +

    If you really want to use an easier syntax you can use an infix name for constructors.

    +
    infixr 5 :::
    +data List a = Nil | a ::: (List a)
    +

    The number after infixr is the priority.

    +

    If you want to be able to print (Show), read (Read), test equality (Eq) and compare (Ord) your new data structure you can tell Haskell to derive the appropriate functions for you.

    +
    +
    infixr 5 :::
    +data List a = Nil | a ::: (List a) 
    +              deriving (Show,Read,Eq,Ord)
    +
    +

    When you add deriving (Show) to your data declaration, Haskell create a show function for you. We’ll see soon how you can use your own show function.

    +
    +
    convertList [] = Nil
    +convertList (x:xs) = x ::: convertList xs
    +
    +
    +
    main = do
    +      print (0 ::: 1 ::: Nil)
    +      print (convertList [0,1])
    +
    +

    This prints:

    +
    0 ::: (1 ::: Nil)
    +0 ::: (1 ::: Nil)
    +

    02_Hard_Part/23_Types.lhs

    +
    +

    02_Hard_Part/30_Trees.lhs

    +

    +Trees +

    + +

    Magritte, l

    +

    We’ll just give another standard example: binary trees.

    +
    +
    import Data.List
    +
    +data BinTree a = Empty
    +                 | Node a (BinTree a) (BinTree a)
    +                              deriving (Show)
    +
    +

    We will also create a function which turns a list into an ordered binary tree.

    +
    +
    treeFromList :: (Ord a) => [a] -> BinTree a
    +treeFromList [] = Empty
    +treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
    +                             (treeFromList (filter (>x) xs))
    +
    +

    Look at how elegant this function is. In plain English:

    +
      +
    • an empty list will be converted to an empty tree.
    • +
    • a list (x:xs) will be converted to a tree where:
    • +
    • The root is x
    • +
    • Its left subtree is the tree created from members of the list xs which are strictly inferior to x and
    • +
    • the right subtree is the tree created from members of the list xs which are strictly superior to x.
    • +
    +
    +
    main = print $ treeFromList [7,2,4,8]
    +
    +

    You should obtain the following:

    +
    Node 7 (Node 2 Empty (Node 4 Empty Empty)) (Node 8 Empty Empty)
    +

    This is an informative but quite unpleasant representation of our tree.

    +

    02_Hard_Part/30_Trees.lhs

    +
    +

    02_Hard_Part/31_Trees.lhs

    +

    Just for fun, let’s code a better display for our trees. I simply had fun making a nice function to display trees in a general way. You can safely skip this part if you find it too difficult to follow.

    +

    We have a few changes to make. We remove the deriving (Show) from the declaration of our BinTree type. And it might also be useful to make our BinTree an instance of (Eq and Ord). We will be able to test equality and compare trees.

    +
    +
    data BinTree a = Empty
    +                 | Node a (BinTree a) (BinTree a)
    +                  deriving (Eq,Ord)
    +
    +

    Without the deriving (Show), Haskell doesn’t create a show method for us. We will create our own version of show. To achieve this, we must declare that our newly created type BinTree a is an instance of the type class Show. The general syntax is:

    +
    instance Show (BinTree a) where
    +   show t = ... -- You declare your function here
    +

    Here is my version of how to show a binary tree. Don’t worry about the apparent complexity. I made a lot of improvements in order to display even stranger objects.

    +
    +
    -- declare BinTree a to be an instance of Show
    +instance (Show a) => Show (BinTree a) where
    +  -- will start by a '<' before the root
    +  -- and put a : a begining of line
    +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
    +    where
    +    -- treeshow pref Tree
    +    --   shows a tree and starts each line with pref
    +    -- We don't display the Empty tree
    +    treeshow pref Empty = ""
    +    -- Leaf
    +    treeshow pref (Node x Empty Empty) =
    +                  (pshow pref x)
    +
    +    -- Right branch is empty
    +    treeshow pref (Node x left Empty) =
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " left)
    +
    +    -- Left branch is empty
    +    treeshow pref (Node x Empty right) =
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- Tree with left and right children non empty
    +    treeshow pref (Node x left right) =
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "|--" "|  " left) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- shows a tree using some prefixes to make it nice
    +    showSon pref before next t =
    +                  pref ++ before ++ treeshow (pref ++ next) t
    +
    +    -- pshow replaces "\n" by "\n"++pref
    +    pshow pref x = replace '\n' ("\n"++pref) (show x)
    +
    +    -- replaces one char by another string
    +    replace c new string =
    +      concatMap (change c new) string
    +      where
    +          change c new x
    +              | x == c = new
    +              | otherwise = x:[] -- "x"
    +
    +

    The treeFromList method remains identical.

    +
    +
    treeFromList :: (Ord a) => [a] -> BinTree a
    +treeFromList [] = Empty
    +treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
    +                             (treeFromList (filter (>x) xs))
    +
    +

    And now, we can play:

    +
    +
    main = do
    +  putStrLn "Int binary tree:"
    +  print $ treeFromList [7,2,4,8,1,3,6,21,12,23]
    +
    +
    Int binary tree:
    +< 7
    +: |--2
    +: |  |--1
    +: |  `--4
    +: |     |--3
    +: |     `--6
    +: `--8
    +:    `--21
    +:       |--12
    +:       `--23
    +

    Now it is far better! The root is shown by starting the line with the < character. And each following line starts with a :. But we could also use another type.

    +
    +
      putStrLn "\nString binary tree:"
    +  print $ treeFromList ["foo","bar","baz","gor","yog"]
    +
    +
    String binary tree:
    +< "foo"
    +: |--"bar"
    +: |  `--"baz"
    +: `--"gor"
    +:    `--"yog"
    +

    As we can test equality and order trees, we can make tree of trees!

    +
    +
      putStrLn "\nBinary tree of Char binary trees:"
    +  print ( treeFromList
    +           (map treeFromList ["baz","zara","bar"]))
    +
    +
    Binary tree of Char binary trees:
    +< < 'b'
    +: : |--'a'
    +: : `--'z'
    +: |--< 'b'
    +: |  : |--'a'
    +: |  : `--'r'
    +: `--< 'z'
    +:    : `--'a'
    +:    :    `--'r'
    +

    This is why I chose to prefix each line of tree display by : (except for the root).

    +

    Yo Dawg Tree

    +
    +
      putStrLn "\nTree of Binary trees of Char binary trees:"
    +  print $ (treeFromList . map (treeFromList . map treeFromList))
    +             [ ["YO","DAWG"]
    +             , ["I","HEARD"]
    +             , ["I","HEARD"]
    +             , ["YOU","LIKE","TREES"] ]
    +
    +

    Which is equivalent to

    +
    print ( treeFromList (
    +          map treeFromList
    +             [ map treeFromList ["YO","DAWG"]
    +             , map treeFromList ["I","HEARD"]
    +             , map treeFromList ["I","HEARD"]
    +             , map treeFromList ["YOU","LIKE","TREES"] ]))
    +

    and gives:

    +
    Binary tree of Binary trees of Char binary trees:
    +< < < 'Y'
    +: : : `--'O'
    +: : `--< 'D'
    +: :    : |--'A'
    +: :    : `--'W'
    +: :    :    `--'G'
    +: |--< < 'I'
    +: |  : `--< 'H'
    +: |  :    : |--'E'
    +: |  :    : |  `--'A'
    +: |  :    : |     `--'D'
    +: |  :    : `--'R'
    +: `--< < 'Y'
    +:    : : `--'O'
    +:    : :    `--'U'
    +:    : `--< 'L'
    +:    :    : `--'I'
    +:    :    :    |--'E'
    +:    :    :    `--'K'
    +:    :    `--< 'T'
    +:    :       : `--'R'
    +:    :       :    |--'E'
    +:    :       :    `--'S'
    +

    Notice how duplicate trees aren’t inserted; there is only one tree corresponding to "I","HEARD". We have this for (almost) free, because we have declared Tree to be an instance of Eq.

    +

    See how awesome this structure is. We can make trees containing not only integers, strings and chars, but also other trees. And we can even make a tree containing a tree of trees!

    +

    02_Hard_Part/31_Trees.lhs

    +
    +

    02_Hard_Part/40_Infinites_Structures.lhs

    +

    +Infinite Structures +

    + +

    Escher

    +

    It is often stated that Haskell is lazy.

    +

    In fact, if you are a bit pedantic, you should state that Haskell is non-strict. Laziness is just a common implementation for non-strict languages.

    +

    Then what does not-strict means? From the Haskell wiki:

    +
    +

    Reduction (the mathematical term for evaluation) proceeds from the outside in.

    +

    so if you have (a+(b*c)) then you first reduce + first, then you reduce the inner (b*c)

    +
    +

    For example in Haskell you can do:

    +
    +
    -- numbers = [1,2,..]
    +numbers :: [Integer]
    +numbers = 0:map (1+) numbers
    +
    +take' n [] = []
    +take' 0 l = []
    +take' n (x:xs) = x:take' (n-1) xs
    +
    +main = print $ take' 10 numbers
    +
    +

    And it stops.

    +

    How?

    +

    Instead of trying to evaluate numbers entirely, it evaluates elements only when needed.

    +

    Also, note in Haskell there is a notation for infinite lists

    +
    [1..]   ⇔ [1,2,3,4...]
    +[1,3..] ⇔ [1,3,5,7,9,11...]
    +

    And most functions will work with them. Also, there is a built-in function take which is equivalent to our take'.

    +

    02_Hard_Part/40_Infinites_Structures.lhs

    +
    +

    02_Hard_Part/41_Infinites_Structures.lhs

    +
    + +

    This code is mostly the same as the previous one.

    +
    +
    import Debug.Trace (trace)
    +import Data.List
    +data BinTree a = Empty 
    +                 | Node a (BinTree a) (BinTree a) 
    +                  deriving (Eq,Ord)
    +
    +
    +
    -- declare BinTree a to be an instance of Show
    +instance (Show a) => Show (BinTree a) where
    +  -- will start by a '<' before the root
    +  -- and put a : a begining of line
    +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
    +    where
    +    treeshow pref Empty = ""
    +    treeshow pref (Node x Empty Empty) = 
    +                  (pshow pref x)
    +
    +    treeshow pref (Node x left Empty) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " left)
    +
    +    treeshow pref (Node x Empty right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    treeshow pref (Node x left right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "|--" "|  " left) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- show a tree using some prefixes to make it nice
    +    showSon pref before next t = 
    +                  pref ++ before ++ treeshow (pref ++ next) t
    +
    +    -- pshow replace "\n" by "\n"++pref
    +    pshow pref x = replace '\n' ("\n"++pref) (" " ++ show x)
    +
    +    -- replace on char by another string
    +    replace c new string =
    +      concatMap (change c new) string
    +      where
    +          change c new x 
    +              | x == c = new
    +              | otherwise = x:[] -- "x"
    +
    +
    + +

    Suppose we don’t mind having an ordered binary tree. Here is an infinite binary tree:

    +
    +
    nullTree = Node 0 nullTree nullTree
    +
    +

    A complete binary tree where each node is equal to 0. Now I will prove you can manipulate this object using the following function:

    +
    +
    -- take all element of a BinTree 
    +-- up to some depth
    +treeTakeDepth _ Empty = Empty
    +treeTakeDepth 0 _     = Empty
    +treeTakeDepth n (Node x left right) = let
    +          nl = treeTakeDepth (n-1) left
    +          nr = treeTakeDepth (n-1) right
    +          in
    +              Node x nl nr
    +
    +

    See what occurs for this program:

    +
    main = print $ treeTakeDepth 4 nullTree
    +

    This code compiles, runs and stops giving the following result:

    +
    <  0
    +: |-- 0
    +: |  |-- 0
    +: |  |  |-- 0
    +: |  |  `-- 0
    +: |  `-- 0
    +: |     |-- 0
    +: |     `-- 0
    +: `-- 0
    +:    |-- 0
    +:    |  |-- 0
    +:    |  `-- 0
    +:    `-- 0
    +:       |-- 0
    +:       `-- 0
    +

    Just to heat up your neurones a bit more, let’s make a slightly more interesting tree:

    +
    +
    iTree = Node 0 (dec iTree) (inc iTree)
    +        where
    +           dec (Node x l r) = Node (x-1) (dec l) (dec r) 
    +           inc (Node x l r) = Node (x+1) (inc l) (inc r) 
    +
    +

    Another way to create this tree is to use a higher order function. This function should be similar to map, but should work on BinTree instead of list. Here is such a function:

    +
    +
    -- apply a function to each node of Tree
    +treeMap :: (a -> b) -> BinTree a -> BinTree b
    +treeMap f Empty = Empty
    +treeMap f (Node x left right) = Node (f x) 
    +                                     (treeMap f left) 
    +                                     (treeMap f right)
    +
    +

    Hint: I won’t talk more about this here. If you are interested by the generalization of map to other data structures, search for functor and fmap.

    +

    Our definition is now:

    +
    +
    infTreeTwo :: BinTree Int
    +infTreeTwo = Node 0 (treeMap (\x -> x-1) infTreeTwo) 
    +                    (treeMap (\x -> x+1) infTreeTwo) 
    +
    +

    Look at the result for

    +
    main = print $ treeTakeDepth 4 infTreeTwo
    +
    <  0
    +: |-- -1
    +: |  |-- -2
    +: |  |  |-- -3
    +: |  |  `-- -1
    +: |  `-- 0
    +: |     |-- -1
    +: |     `-- 1
    +: `-- 1
    +:    |-- 0
    +:    |  |-- -1
    +:    |  `-- 1
    +:    `-- 2
    +:       |-- 1
    +:       `-- 3
    +
    + +
    +
    main = do
    +  print $ treeTakeDepth 4 nullTree
    +  print $ treeTakeDepth 4 infTreeTwo
    +
    +
    + +

    02_Hard_Part/41_Infinites_Structures.lhs

    +

    +Hell Difficulty Part +

    + +

    Congratulations for getting so far! Now, some of the really hardcore stuff can start.

    +

    If you are like me, you should get the functional style. You should also understand a bit more the advantages of laziness by default. But you also don’t really understand where to start in order to make a real program. And in particular:

    +
      +
    • How do you deal with effects?
    • +
    • Why is there a strange imperative-like notation for dealing with IO?
    • +
    +

    Be prepared, the answers might be complex. But they all be very rewarding.

    +
    +

    03_Hell/01_IO/01_progressive_io_example.lhs

    +

    +Deal With IO +

    + +

    Magritte, Carte blanche

    +
    +

    tl;dr:

    +

    A typical function doing IO looks a lot like an imperative program:

    +
    f :: IO a
    +f = do
    +  x <- action1
    +  action2 x
    +  y <- action3
    +  action4 x y
    +
      +
    • To set a value to an object we use <- .
    • +
    • The type of each line is IO *; in this example:
    • +
    • action1 :: IO b
    • +
    • action2 x :: IO ()
    • +
    • action3 :: IO c
    • +
    • action4 x y :: IO a
    • +
    • x :: b, y :: c
    • +
    • Few objects have the type IO a, this should help you choose. In particular you cannot use pure functions directly here. To use pure functions you could do action2 (purefunction x) for example.
    • +
    +
    +

    In this section, I will explain how to use IO, not how it works. You’ll see how Haskell separates the pure from the impure parts of the program.

    +

    Don’t stop because you’re trying to understand the details of the syntax. Answers will come in the next section.

    +

    What to achieve?

    +
    +

    Ask a user to enter a list of numbers. Print the sum of the numbers

    +
    +
    +
    toList :: String -> [Integer]
    +toList input = read ("[" ++ input ++ "]")
    +
    +main = do
    +  putStrLn "Enter a list of numbers (separated by comma):"
    +  input <- getLine
    +  print $ sum (toList input)
    +
    +

    It should be straightforward to understand the behavior of this program. Let’s analyze the types in more detail.

    +
    putStrLn :: String -> IO ()
    +getLine  :: IO String
    +print    :: Show a => a -> IO ()
    +

    Or more interestingly, we note that each expression in the do block has a type of IO a.

    +
    +main = do
    +  putStrLn "Enter ... " :: IO ()
    +  getLine               :: IO String
    +  print Something       :: IO ()
    +
    + +

    We should also pay attention to the effect of the <- symbol.

    +
    do
    + x <- something
    +

    If something :: IO a then x :: a.

    +

    Another important note about using IO. All lines in a do block must be of one of the two forms:

    +
    action1             :: IO a
    +                    -- in this case, generally a = ()
    +

    or

    +
    value <- action2    -- where
    +                    -- bar z t :: IO b
    +                    -- value   :: b
    +

    These two kinds of line will correspond to two different ways of sequencing actions. The meaning of this sentence should be clearer by the end of the next section.

    +

    03_Hell/01_IO/01_progressive_io_example.lhs

    +
    +

    03_Hell/01_IO/02_progressive_io_example.lhs

    +

    Now let’s see how this program behaves. For example, what occur if the user enter something strange? Let’s try:

    +
        % runghc 02_progressive_io_example.lhs
    +    Enter a list of numbers (separated by comma):
    +    foo
    +    Prelude.read: no parse
    +

    Argh! An evil error message and a crash! The first evolution will be to answer with a more friendly message.

    +

    In order to do this, we must detect that something went wrong. Here is one way to do this. Use the type Maybe. It is a very common type in Haskell.

    +
    +
    import Data.Maybe
    +
    +

    What is this thing? Maybe is a type which takes one parameter. Its definition is:

    +
    data Maybe a = Nothing | Just a
    +

    This is a nice way to tell there was an error while trying to create/compute a value. The maybeRead function is a great example of this. This is a function similar to the function read5, but if something goes wrong the returned value is Nothing. If the value is right, it returns Just <the value>. Don’t try to understand too much of this function. I use a lower level function than read; reads.

    +
    +
    maybeRead :: Read a => String -> Maybe a
    +maybeRead s = case reads s of
    +                  [(x,"")]    -> Just x
    +                  _           -> Nothing
    +
    +

    Now to be a bit more readable, we define a function which goes like this: If the string has the wrong format, it will return Nothing. Otherwise, for example for “1,2,3”, it will return Just [1,2,3].

    +
    +
    getListFromString :: String -> Maybe [Integer]
    +getListFromString str = maybeRead $ "[" ++ str ++ "]"
    +
    +

    We simply have to test the value in our main function.

    +
    +
    main :: IO ()
    +main = do
    +  putStrLn "Enter a list of numbers (separated by comma):"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> print (sum l)
    +          Nothing -> error "Bad format. Good Bye."
    +
    +

    In case of error, we display a nice error message.

    +

    Note that the type of each expression in the main’s do block remains of the form IO a. The only strange construction is error. I’ll say error msg will simply take the needed type (here IO ()).

    +

    One very important thing to note is the type of all the functions defined so far. There is only one function which contains IO in its type: main. This means main is impure. But main uses getListFromString which is pure. It is then clear just by looking at declared types which functions are pure and which are impure.

    +

    Why does purity matter? I certainly forget many advantages, but the three main reasons are:

    +
      +
    • It is far easier to think about pure code than impure one.
    • +
    • Purity protects you from all the hard to reproduce bugs due to side effects.
    • +
    • You can evaluate pure functions in any order or in parallel without risk.
    • +
    +

    This is why you should generally put as most code as possible inside pure functions.

    +

    03_Hell/01_IO/02_progressive_io_example.lhs

    +
    +

    03_Hell/01_IO/03_progressive_io_example.lhs

    +

    Our next evolution will be to prompt the user again and again until she enters a valid answer.

    +

    We keep the first part:

    +
    +
    import Data.Maybe
    +
    +maybeRead :: Read a => String -> Maybe a
    +maybeRead s = case reads s of
    +                  [(x,"")]    -> Just x
    +                  _           -> Nothing
    +getListFromString :: String -> Maybe [Integer]
    +getListFromString str = maybeRead $ "[" ++ str ++ "]"
    +
    +

    Now, we create a function which will ask the user for an list of integers until the input is right.

    +
    +
    askUser :: IO [Integer]
    +askUser = do
    +  putStrLn "Enter a list of numbers (separated by comma):"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> return l
    +          Nothing -> askUser
    +
    +

    This function is of type IO [Integer]. Such a type means that we retrieved a value of type [Integer] through some IO actions. Some people might explain while waving their hands:

    +
    +

    «This is an [Integer] inside an IO»

    +
    +

    If you want to understand the details behind all of this, you’ll have to read the next section. But sincerely, if you just want to use IO. Just practice a little and remember to think about the type.

    +

    Finally our main function is quite simpler:

    +
    +
    main :: IO ()
    +main = do
    +  list <- askUser
    +  print $ sum list
    +
    +

    We have finished with our introduction to IO. This was quite fast. Here are the main things to remember:

    +
      +
    • in the do bloc, each expression must have the type IO a. You are then limited in the number of expressions available. For example, getLine, print, putStrLn, etc…
    • +
    • Try to externalize the pure functions as much as possible.
    • +
    • the IO a type means: an IO action which returns an element of type a. IO represents actions; under the hood, IO a is the type of a function. Read the next section if you are curious.
    • +
    +

    If you practice a bit, you should be able to use IO.

    +
    +

    Exercises:

    +
      +
    • Make a program that sums all of its arguments. Hint: use the function getArgs.
    • +
    +
    +

    03_Hell/01_IO/03_progressive_io_example.lhs

    +

    +IO trick explained +

    + +

    Magritte, ceci n

    +
    +

    Here is a tl;dr: for this section.

    +

    To separate pure and impure parts, main is defined as a function which modifies the state of the world

    +
    main :: World -> World
    +

    A function is guaranteed to have side effects only if it has this type. But look at a typical main function:

    +
    main w0 =
    +    let (v1,w1) = action1 w0 in
    +    let (v2,w2) = action2 v1 w1 in
    +    let (v3,w3) = action3 v2 w2 in
    +    action4 v3 w3
    +

    We have a lot of temporary elements (here w1, w2 and w3) which must be passed on to the next action.

    +

    We create a function bind or (>>=). With bind we don’t need temporary names anymore.

    +
    main =
    +  action1 >>= action2 >>= action3 >>= action4
    +

    Bonus: Haskell has syntactical sugar for us:

    +
    main = do
    +  v1 <- action1
    +  v2 <- action2 v1
    +  v3 <- action3 v2
    +  action4 v3
    +
    +

    Why did we use this strange syntax, and what exactly is this IO type? It looks a bit like magic.

    +

    For now let’s just forget all about the pure parts of our program, and focus on the impure parts:

    +
    askUser :: IO [Integer]
    +askUser = do
    +  putStrLn "Enter a list of numbers (separated by commas):"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> return l
    +          Nothing -> askUser
    +
    +main :: IO ()
    +main = do
    +  list <- askUser
    +  print $ sum list
    +

    First remark; it looks like an imperative structure. Haskell is powerful enough to make impure code look imperative. For example, if you wish you could create a while in Haskell. In fact, for dealing with IO, imperative style is generally more appropriate.

    +

    But you should had noticed the notation is a bit unusual. Here is why, in detail.

    +

    In an impure language, the state of the world can be seen as a huge hidden global variable. This hidden variable is accessible by all functions of your language. For example, you can read and write a file in any function. The fact that a file exists or not can be seen as different states of the world.

    +

    For Haskell this state is not hidden. It is explicitly said main is a function that potentially changes the state of the world. Its type is then something like:

    +
    main :: World -> World
    +

    Not all functions may have access to this variable. Those which have access to this variable are impure. Functions to which the world variable isn’t provided are pure6.

    +

    Haskell considers the state of the world as an input variable to main. But the real type of main is closer to this one7:

    +
    main :: World -> ((),World)
    +

    The () type is the null type. Nothing to see here.

    +

    Now let’s rewrite our main function with this in mind:

    +
    main w0 =
    +    let (list,w1) = askUser w0 in
    +    let (x,w2) = print (sum list,w1) in
    +    x
    +

    First, we note that all functions which have side effects must have the type:

    +
    World -> (a,World)
    +

    Where a is the type of the result. For example, a getChar function should have the type World -> (Char,World).

    +

    Another thing to note is the trick to fix the order of evaluation. In Haskell, in order to evaluate f a b, you have many choices:

    +
      +
    • first eval a then b then f a b
    • +
    • first eval b then a then f a b.
    • +
    • eval a and b in parallel then f a b
    • +
    +

    This is true, because we should work in a pure language.

    +

    Now, if you look at the main function, it is clear you must eval the first line before the second one since, to evaluate the second line you have to get a parameter given by the evaluation of the first line.

    +

    Such trick works nicely. The compiler will at each step provide a pointer to a new real world id. Under the hood, print will evaluate as:

    +
      +
    • print something on the screen
    • +
    • modify the id of the world
    • +
    • evaluate as ((),new world id).
    • +
    +

    Now, if you look at the style of the main function, it is clearly awkward. Let’s try to do the same to the askUser function:

    +
    askUser :: World -> ([Integer],World)
    +

    Before:

    +
    askUser :: IO [Integer]
    +askUser = do
    +  putStrLn "Enter a list of numbers:"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> return l
    +          Nothing -> askUser
    +

    After:

    +
    askUser w0 =
    +    let (_,w1)     = putStrLn "Enter a list of numbers:" in
    +    let (input,w2) = getLine w1 in
    +    let (l,w3)     = case getListFromString input of
    +                      Just l   -> (l,w2)
    +                      Nothing  -> askUser w2
    +    in
    +        (l,w3)
    +

    This is similar, but awkward. Look at all these temporary w? names.

    +

    The lesson, is, naive IO implementation in Pure functional languages is awkward!

    +

    Fortunately, there is a better way to handle this problem. We see a pattern. Each line is of the form:

    +
    let (y,w') = action x w in
    +

    Even if for some line the first x argument isn’t needed. The output type is a couple, (answer, newWorldValue). Each function f must have a type similar to:

    +
    f :: World -> (a,World)
    +

    Not only this, but we can also note that we always follow the same usage pattern:

    +
    let (y,w1) = action1 w0 in
    +let (z,w2) = action2 w1 in
    +let (t,w3) = action3 w2 in
    +...
    +

    Each action can take from 0 to n parameters. And in particular, each action can take a parameter from the result of a line above.

    +

    For example, we could also have:

    +
    let (_,w1) = action1 x w0   in
    +let (z,w2) = action2 w1     in
    +let (_,w3) = action3 x z w2 in
    +...
    +

    And of course actionN w :: (World) -> (a,World).

    +
    +

    IMPORTANT, there are only two important patterns to consider:

    +
    let (x,w1) = action1 w0 in
    +let (y,w2) = action2 x w1 in
    +

    and

    +
    let (_,w1) = action1 w0 in
    +let (y,w2) = action2 w1 in
    +
    +

    Jocker pencil trick

    +

    Now, we will do a magic trick. We will make the temporary world symbol “disappear”. We will bind the two lines. Let’s define the bind function. Its type is quite intimidating at first:

    +
    bind :: (World -> (a,World))
    +        -> (a -> (World -> (b,World)))
    +        -> (World -> (b,World))
    +

    But remember that (World -> (a,World)) is the type for an IO action. Now let’s rename it for clarity:

    +
    type IO a = World -> (a, World)
    +

    Some example of functions:

    +
    getLine :: IO String
    +print :: Show a => a -> IO ()
    +

    getLine is an IO action which takes a world as parameter and returns a couple (String,World). Which can be summarized as: getLine is of type IO String. Which we also see as, an IO action which will return a String “embeded inside an IO”.

    +

    The function print is also interesting. It takes one argument which can be shown. In fact it takes two arguments. The first is the value to print and the other is the state of world. It then returns a couple of type ((),World). This means it changes the state of the world, but doesn’t yield anymore data.

    +

    This type helps us simplify the type of bind:

    +
    bind :: IO a
    +        -> (a -> IO b)
    +        -> IO b
    +

    It says that bind takes two IO actions as parameter and return another IO action.

    +

    Now, remember the important patterns. The first was:

    +
    let (x,w1) = action1 w0 in
    +let (y,w2) = action2 x w1 in
    +(y,w2)
    +

    Look at the types:

    +
    action1  :: IO a
    +action2  :: a -> IO b
    +(y,w2)   :: IO b
    +

    Doesn’t it seem familiar?

    +
    (bind action1 action2) w0 =
    +    let (x, w1) = action1 w0
    +        (y, w2) = action2 x w1
    +    in  (y, w2)
    +

    The idea is to hide the World argument with this function. Let’s go: As an example imagine if we wanted to simulate:

    +
    let (line1,w1) = getLine w0 in
    +let ((),w2) = print line1 in
    +((),w2)
    +

    Now, using the bind function:

    +
    (res,w2) = (bind getLine (\l -> print l)) w0
    +

    As print is of type (World -> ((),World)), we know res = () (null type). If you didn’t see what was magic here, let’s try with three lines this time.

    +
    let (line1,w1) = getLine w0 in
    +let (line2,w2) = getLine w1 in
    +let ((),w3) = print (line1 ++ line2) in
    +((),w3)
    +

    Which is equivalent to:

    +
    (res,w3) = bind getLine (\line1 ->
    +             bind getLine (\line2 ->
    +               print (line1 ++ line2)))
    +

    Didn’t you notice something? Yes, no temporary World variables are used anywhere! This is MA. GIC.

    +

    We can use a better notation. Let’s use (>>=) instead of bind. (>>=) is an infix function like (+); reminder 3 + 4 ⇔ (+) 3 4

    +
    (res,w3) = getLine >>=
    +           \line1 -> getLine >>=
    +           \line2 -> print (line1 ++ line2)
    +

    Ho Ho Ho! Happy Christmas Everyone! Haskell has made syntactical sugar for us:

    +
    do
    +  x <- action1
    +  y <- action2
    +  z <- action3
    +  ...
    +

    Is replaced by:

    +
    action1 >>= \x ->
    +action2 >>= \y ->
    +action3 >>= \z ->
    +...
    +

    Note you can use x in action2 and x and y in action3.

    +

    But what about the lines not using the <-? Easy, another function blindBind:

    +
    blindBind :: IO a -> IO b -> IO b
    +blindBind action1 action2 w0 =
    +    bind action (\_ -> action2) w0
    +

    I didn’t simplify this definition for clarity purpose. Of course we can use a better notation, we’ll use the (>>) operator.

    +

    And

    +
    do
    +    action1
    +    action2
    +    action3
    +

    Is transformed into

    +
    action1 >>
    +action2 >>
    +action3
    +

    Also, another function is quite useful.

    +
    putInIO :: a -> IO a
    +putInIO x = IO (\w -> (x,w))
    +

    This is the general way to put pure values inside the “IO context”. The general name for putInIO is return. This is quite a bad name when you learn Haskell. return is very different from what you might be used to.

    +
    +

    03_Hell/01_IO/21_Detailled_IO.lhs

    +

    To finish, let’s translate our example:

    +
    
    +askUser :: IO [Integer]
    +askUser = do
    +  putStrLn "Enter a list of numbers (separated by commas):"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> return l
    +          Nothing -> askUser
    +
    +main :: IO ()
    +main = do
    +  list <- askUser
    +  print $ sum list
    +

    Is translated into:

    +
    +
    import Data.Maybe
    +
    +maybeRead :: Read a => String -> Maybe a
    +maybeRead s = case reads s of
    +                  [(x,"")]    -> Just x
    +                  _           -> Nothing
    +getListFromString :: String -> Maybe [Integer]
    +getListFromString str = maybeRead $ "[" ++ str ++ "]"
    +askUser :: IO [Integer]
    +askUser = 
    +    putStrLn "Enter a list of numbers (sep. by commas):" >>
    +    getLine >>= \input ->
    +    let maybeList = getListFromString input in
    +      case maybeList of
    +        Just l -> return l
    +        Nothing -> askUser
    +
    +main :: IO ()
    +main = askUser >>=
    +  \list -> print $ sum list
    +
    +

    You can compile this code to verify it keeps working.

    +

    Imagine what it would look like without the (>>) and (>>=).

    +

    03_Hell/01_IO/21_Detailled_IO.lhs

    +
    +

    03_Hell/02_Monads/10_Monads.lhs

    +

    +Monads +

    + +

    Dali, reve. It represents a weapon out of the mouth of a tiger, itself out of the mouth of another tiger, itself out of the mouth of a fish itself out of a grenade. I could have choosen a picture of the Human centipede as it is a very good representation of what a monad really is. But just to thing about it, I find this disgusting and that wasn

    +

    Now the secret can be revealed: IO is a monad. Being a monad means you have access to some syntactical sugar with the do notation. But mainly, you have access to a coding pattern which will ease the flow of your code.

    +
    +

    Important remarks:

    +
      +
    • Monad are not necessarily about effects! There are a lot of pure monads.
    • +
    • Monad are more about sequencing
    • +
    +
    +

    For the Haskell language Monad is a type class. To be an instance of this type class, you must provide the functions (>>=) and return. The function (>>) will be derived from (>>=). Here is how the type class Monad is declared (mostly):

    +
    class Monad m  where
    +  (>>=) :: m a -> (a -> m b) -> m b
    +  return :: a -> m a
    +
    +  (>>) :: m a -> m b -> m b
    +  f >> g = f >>= \_ -> g
    +
    +  -- You should generally safely ignore this function
    +  -- which I believe exists for historical reason
    +  fail :: String -> m a
    +  fail = error
    +
    +

    Remarks:

    +
      +
    • the keyword class is not your friend. A Haskell class is not a class like in object model. A Haskell class has a lot of similarities with Java interfaces. A better word should have been typeclass. That means a set of types. For a type to belong to a class, all functions of the class must be provided for this type.
    • +
    • In this particular example of type class, the type m must be a type that takes an argument. for example IO a, but also Maybe a, [a], etc…
    • +
    • To be a useful monad, your function must obey some rules. If your construction does not obey these rules strange things might happens:
    • +
    +

    ~ return a >>= k == k a m >>= return == m m >>= (-> k x >>= h) == (m >>= k) >>= h ~

    +
    +

    +Maybe is a monad +

    + +

    There are a lot of different types that are instance of Monad. One of the easiest to describe is Maybe. If you have a sequence of Maybe values, you can use monads to manipulate them. It is particularly useful to remove very deep if..then..else.. constructions.

    +

    Imagine a complex bank operation. You are eligible to gain about 700€ only if you can afford to follow a list of operations without being negative.

    +
    +
    deposit  value account = account + value
    +withdraw value account = account - value
    +
    +eligible :: (Num a,Ord a) => a -> Bool
    +eligible account =
    +  let account1 = deposit 100 account in
    +    if (account1 < 0)
    +    then False
    +    else
    +      let account2 = withdraw 200 account1 in
    +      if (account2 < 0)
    +      then False
    +      else
    +        let account3 = deposit 100 account2 in
    +        if (account3 < 0)
    +        then False
    +        else
    +          let account4 = withdraw 300 account3 in
    +          if (account4 < 0)
    +          then False
    +          else
    +            let account5 = deposit 1000 account4 in
    +            if (account5 < 0)
    +            then False
    +            else
    +              True
    +
    +main = do
    +  print $ eligible 300 -- True
    +  print $ eligible 299 -- False
    +
    +

    03_Hell/02_Monads/10_Monads.lhs

    +
    +

    03_Hell/02_Monads/11_Monads.lhs

    +

    Now, let’s make it better using Maybe and the fact that it is a Monad

    +
    +
    deposit :: (Num a) => a -> a -> Maybe a
    +deposit value account = Just (account + value)
    +
    +withdraw :: (Num a,Ord a) => a -> a -> Maybe a
    +withdraw value account = if (account < value) 
    +                         then Nothing 
    +                         else Just (account - value)
    +
    +eligible :: (Num a, Ord a) => a -> Maybe Bool
    +eligible account = do
    +  account1 <- deposit 100 account 
    +  account2 <- withdraw 200 account1 
    +  account3 <- deposit 100 account2 
    +  account4 <- withdraw 300 account3 
    +  account5 <- deposit 1000 account4
    +  Just True
    +
    +main = do
    +  print $ eligible 300 -- Just True
    +  print $ eligible 299 -- Nothing
    +
    +

    03_Hell/02_Monads/11_Monads.lhs

    +
    +

    03_Hell/02_Monads/12_Monads.lhs

    +

    Not bad, but we can make it even better:

    +
    +
    deposit :: (Num a) => a -> a -> Maybe a
    +deposit value account = Just (account + value)
    +
    +withdraw :: (Num a,Ord a) => a -> a -> Maybe a
    +withdraw value account = if (account < value) 
    +                         then Nothing 
    +                         else Just (account - value)
    +
    +eligible :: (Num a, Ord a) => a -> Maybe Bool
    +eligible account =
    +  deposit 100 account >>=
    +  withdraw 200 >>=
    +  deposit 100  >>=
    +  withdraw 300 >>=
    +  deposit 1000 >>
    +  return True
    +
    +main = do
    +  print $ eligible 300 -- Just True
    +  print $ eligible 299 -- Nothing
    +
    +

    We have proven that Monads are a good way to make our code more elegant. Note this idea of code organization, in particular for Maybe can be used in most imperative language. In fact, this is the kind of construction we make naturally.

    +
    +

    An important remark:

    +

    The first element in the sequence being evaluated to Nothing will stop the complete evaluation. This means you don’t execute all lines. You have this for free, thanks to laziness.

    +
    +

    You could also replay these example with the definition of (>>=) for Maybe in mind:

    +
    instance Monad Maybe where
    +    (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
    +    Nothing  >>= _  = Nothing
    +    (Just x) >>= f  = f x
    +
    +    return x = Just x
    +

    The Maybe monad proved to be useful while being a very simple example. We saw the utility of the IO monad. But now a cooler example, lists.

    +

    03_Hell/02_Monads/12_Monads.lhs

    +
    +

    03_Hell/02_Monads/13_Monads.lhs

    +

    +The list monad +

    + +

    Golconde de Magritte

    +

    The list monad helps us to simulate non deterministic computations. Here we go:

    +
    +
    import Control.Monad (guard)
    +
    +allCases = [1..10]
    +
    +resolve :: [(Int,Int,Int)]
    +resolve = do
    +              x <- allCases
    +              y <- allCases
    +              z <- allCases
    +              guard $ 4*x + 2*y < z
    +              return (x,y,z)
    +
    +main = do
    +  print resolve
    +
    +

    MA. GIC. :

    +
    [(1,1,7),(1,1,8),(1,1,9),(1,1,10),(1,2,9),(1,2,10)]
    +

    For the list monad, there is also a syntactical sugar:

    +
    +
      print $ [ (x,y,z) | x <- allCases,
    +                      y <- allCases,
    +                      z <- allCases,
    +                      4*x + 2*y < z ]
    +
    +

    I won’t list all the monads, but there are many monads. Using monads simplifies the manipulation of several notions in pure languages. In particular, monad are very useful for:

    +
      +
    • IO,
    • +
    • non deterministic computation,
    • +
    • generating pseudo random numbers,
    • +
    • keeping configuration state,
    • +
    • writing state,
    • +
    • +
    +

    If you have followed me until here, then you’ve done it! You know monads8!

    +

    03_Hell/02_Monads/13_Monads.lhs

    +

    +Appendix +

    + +

    This section is not so much about learning Haskell. It is just here to discuss some details further.

    +
    +

    04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs

    +

    +More on Infinite Tree +

    + +

    In the section Infinite Structures we saw some simple constructions. Unfortunately we removed two properties from our tree:

    +
      +
    1. no duplicate node value
    2. +
    3. well ordered tree
    4. +
    +

    In this section we will try to keep the first property. Concerning the second one, we must relax it but we’ll discuss how to keep it as much as possible.

    +
    + +

    This code is mostly the same as the one in the tree section.

    +
    +
    import Data.List
    +data BinTree a = Empty 
    +                 | Node a (BinTree a) (BinTree a) 
    +                  deriving (Eq,Ord)
    +
    +-- declare BinTree a to be an instance of Show
    +instance (Show a) => Show (BinTree a) where
    +  -- will start by a '<' before the root
    +  -- and put a : a begining of line
    +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
    +    where
    +    treeshow pref Empty = ""
    +    treeshow pref (Node x Empty Empty) = 
    +                  (pshow pref x)
    +
    +    treeshow pref (Node x left Empty) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " left)
    +
    +    treeshow pref (Node x Empty right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    treeshow pref (Node x left right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "|--" "|  " left) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- show a tree using some prefixes to make it nice
    +    showSon pref before next t = 
    +                  pref ++ before ++ treeshow (pref ++ next) t
    +
    +    -- pshow replace "\n" by "\n"++pref
    +    pshow pref x = replace '\n' ("\n"++pref) (show x)
    +
    +    -- replace on char by another string
    +    replace c new string =
    +      concatMap (change c new) string
    +      where
    +          change c new x 
    +              | x == c = new
    +              | otherwise = x:[] -- "x"
    +
    +
    + +

    Our first step is to create some pseudo-random number list:

    +
    +
    shuffle = map (\x -> (x*3123) `mod` 4331) [1..]
    +
    +

    Just as a reminder, here is the definition of treeFromList

    +
    +
    treeFromList :: (Ord a) => [a] -> BinTree a
    +treeFromList []    = Empty
    +treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
    +                             (treeFromList (filter (>x) xs))
    +
    +

    and treeTakeDepth:

    +
    +
    treeTakeDepth _ Empty = Empty
    +treeTakeDepth 0 _     = Empty
    +treeTakeDepth n (Node x left right) = let
    +          nl = treeTakeDepth (n-1) left
    +          nr = treeTakeDepth (n-1) right
    +          in
    +              Node x nl nr
    +
    +

    See the result of:

    +
    +
    main = do
    +      putStrLn "take 10 shuffle"
    +      print $ take 10 shuffle
    +      putStrLn "\ntreeTakeDepth 4 (treeFromList shuffle)"
    +      print $ treeTakeDepth 4 (treeFromList shuffle)
    +
    +
    % runghc 02_Hard_Part/41_Infinites_Structures.lhs
    +take 10 shuffle
    +[3123,1915,707,3830,2622,1414,206,3329,2121,913]
    +treeTakeDepth 4 (treeFromList shuffle)
    +
    +< 3123
    +: |--1915
    +: |  |--707
    +: |  |  |--206
    +: |  |  `--1414
    +: |  `--2622
    +: |     |--2121
    +: |     `--2828
    +: `--3830
    +:    |--3329
    +:    |  |--3240
    +:    |  `--3535
    +:    `--4036
    +:       |--3947
    +:       `--4242
    +

    Yay! It ends! Beware though, it will only work if you always have something to put into a branch.

    +

    For example

    +
    treeTakeDepth 4 (treeFromList [1..]) 
    +

    will loop forever. Simply because it will try to access the head of filter (<1) [2..]. But filter is not smart enought to understand that the result is the empty list.

    +

    Nonetheless, it is still a very cool example of what non strict programs have to offer.

    +

    Left as an exercise to the reader:

    +
      +
    • Prove the existence of a number n so that treeTakeDepth n (treeFromList shuffle) will enter an infinite loop.
    • +
    • Find an upper bound for n.
    • +
    • Prove there is no shuffle list so that, for any depth, the program ends.
    • +
    +

    04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs

    +
    +

    04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs

    +
    + +

    This code is mostly the same as the preceding one.

    +
    +
    import Debug.Trace (trace)
    +import Data.List
    +data BinTree a = Empty 
    +                 | Node a (BinTree a) (BinTree a) 
    +                  deriving (Eq,Ord)
    +
    +
    +
    -- declare BinTree a to be an instance of Show
    +instance (Show a) => Show (BinTree a) where
    +  -- will start by a '<' before the root
    +  -- and put a : a begining of line
    +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
    +    where
    +    treeshow pref Empty = ""
    +    treeshow pref (Node x Empty Empty) = 
    +                  (pshow pref x)
    +
    +    treeshow pref (Node x left Empty) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " left)
    +
    +    treeshow pref (Node x Empty right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    treeshow pref (Node x left right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "|--" "|  " left) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- show a tree using some prefixes to make it nice
    +    showSon pref before next t = 
    +                  pref ++ before ++ treeshow (pref ++ next) t
    +
    +    -- pshow replace "\n" by "\n"++pref
    +    pshow pref x = replace '\n' ("\n"++pref) (" " ++ show x)
    +
    +    -- replace on char by another string
    +    replace c new string =
    +      concatMap (change c new) string
    +      where
    +          change c new x 
    +              | x == c = new
    +              | otherwise = x:[] -- "x"
    +
    +treeTakeDepth _ Empty = Empty
    +treeTakeDepth 0 _     = Empty
    +treeTakeDepth n (Node x left right) = let
    +          nl = treeTakeDepth (n-1) left
    +          nr = treeTakeDepth (n-1) right
    +          in
    +              Node x nl nr
    +
    +
    + +

    In order to resolve these problem we will modify slightly our treeFromList and shuffle function.

    +

    A first problem, is the lack of infinite different number in our implementation of shuffle. We generated only 4331 different numbers. To resolve this we make a slightly better shuffle function.

    +
    +
    shuffle = map rand [1..]
    +          where 
    +              rand x = ((p x) `mod` (x+c)) - ((x+c) `div` 2)
    +              p x = m*x^2 + n*x + o -- some polynome
    +              m = 3123    
    +              n = 31
    +              o = 7641
    +              c = 1237
    +
    +

    This shuffle function has the property (hopefully) not to have an upper nor lower bound. But having a better shuffle list isn’t enough not to enter an infinite loop.

    +

    Generally, we cannot decide whether filter (<x) xs is empty. Then to resolve this problem, I’ll authorize some error in the creation of our binary tree. This new version of code can create binary tree which don’t have the following property for some of its nodes:

    +
    +

    Any element of the left (resp. right) branch must all be strictly inferior (resp. superior) to the label of the root.

    +
    +

    Remark it will remains mostly an ordered binary tree. Furthermore, by construction, each node value is unique in the tree.

    +

    Here is our new version of treeFromList. We simply have replaced filter by safefilter.

    +
    +
    treeFromList :: (Ord a, Show a) => [a] -> BinTree a
    +treeFromList []    = Empty
    +treeFromList (x:xs) = Node x left right
    +          where 
    +              left = treeFromList $ safefilter (<x) xs
    +              right = treeFromList $ safefilter (>x) xs
    +
    +

    This new function safefilter is almost equivalent to filter but don’t enter infinite loop if the result is a finite list. If it cannot find an element for which the test is true after 10000 consecutive steps, then it considers to be the end of the search.

    +
    +
    safefilter :: (a -> Bool) -> [a] -> [a]
    +safefilter f l = safefilter' f l nbTry
    +  where
    +      nbTry = 10000
    +      safefilter' _ _ 0 = []
    +      safefilter' _ [] _ = []
    +      safefilter' f (x:xs) n = 
    +                  if f x 
    +                     then x : safefilter' f xs nbTry 
    +                     else safefilter' f xs (n-1) 
    +
    +

    Now run the program and be happy:

    +
    +
    main = do
    +      putStrLn "take 10 shuffle"
    +      print $ take 10 shuffle
    +      putStrLn "\ntreeTakeDepth 8 (treeFromList shuffle)"
    +      print $ treeTakeDepth 8 (treeFromList $ shuffle)
    +
    +

    You should realize the time to print each value is different. This is because Haskell compute each value when it needs it. And in this case, this is when asked to print it on the screen.

    +

    Impressively enough, try to replace the depth from 8 to 100. It will work without killing your RAM! The flow and the memory management is done naturally by Haskell.

    +

    Left as an exercise to the reader:

    +
      +
    • Even with large constant value for deep and nbTry, it seems to work nicely. But in the worst case, it can be exponential. Create a worst case list to give as parameter to treeFromList.
      hint: think about ([0,-1,-1,....,-1,1,-1,...,-1,1,...]).
    • +
    • I first tried to implement safefilter as follow: +
      +  safefilter' f l = if filter f (take 10000 l) == []
      +                    then []
      +                    else filter f l
      +  
      + +Explain why it doesn’t work and can enter into an infinite loop.
    • +
    • Suppose that shuffle is real random list with growing bounds. If you study a bit this structure, you’ll discover that with probability 1, this structure is finite. Using the following code (suppose we could use safefilter' directly as if was not in the where of safefilter) find a definition of f such that with probability 1, treeFromList’ shuffle is infinite. And prove it. Disclaimer, this is only a conjecture.
    • +
    +
    treeFromList' []  n = Empty
    +treeFromList' (x:xs) n = Node x left right
    +    where
    +        left = treeFromList' (safefilter' (<x) xs (f n)
    +        right = treeFromList' (safefilter' (>x) xs (f n)
    +        f = ???
    +

    04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs

    +

    Thanks

    +

    Thanks to /r/haskell and /r/programming. Your comment were most than welcome.

    +

    Particularly, I want to thank Emm a thousand times for the time he spent on correcting my English. Thank you man.

    +
    +
    +
      +
    1. Même si tous les langages récents essayent de les cacher, ils restent présents.

    2. +
    3. I know I’m cheating. But I will talk about non-strict later.

    4. +
    5. For the brave, a more complete explanation of pattern matching can be found here.

    6. +
    7. You should remark squareEvenSum'' is more efficient that the two other versions. The order of (.) is important.

    8. +
    9. Which itself is very similar to the javascript eval on a string containing JSON).

    10. +
    11. There are some unsafe exceptions to this rule. But you shouldn’t see such use on a real application except maybe for debugging purpose.

    12. +
    13. For the curious the real type is data IO a = IO {unIO :: State# RealWorld -> (# State# RealWorld, a #)}. All the # as to do with optimisation and I swapped the fields in my example. But mostly, the idea is exactly the same.

    14. +
    15. Well, you’ll certainly need to practice a bit to get used to them and to understand when you can use them and create your own. But you already made a big step in this direction.

    16. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2012-02-08 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/Higher-order-function-in-zsh/index.html b/Scratch/fr/blog/Higher-order-function-in-zsh/index.html new file mode 100644 index 0000000..78b762e --- /dev/null +++ b/Scratch/fr/blog/Higher-order-function-in-zsh/index.html @@ -0,0 +1,273 @@ + + + + + + YBlog - Fonctions d'ordre supérieur en zsh + + + + + + + + + + + + + +
    + + +
    +

    Fonctions d'ordre supérieur en zsh

    +
    +
    +
    +
    +

    Title image

    +
    + +

    UPDATE: Nicholas Sterling a découvert un moyen de faire des fonctions anonymes Merci!

    +

    Avec cette dernière version vous pouvez utiliser map si vous utilisez des fonctions déclarées. mapl pour les fonctions anonymes et mapa pour les fonctions arithmétiques.

    +

    Exemple :

    +
    $ filterl 'echo $1|grep a >/dev/null' ab cd ef ada
    +ab
    +ada
    +
    +$ folda '$1+$2' {1..5}
    +15
    +
    +$ folda '$1*$2' {1..20}
    +2432902008176640000
    +
    +$ mapl 'echo X $1:t Y' ~/.zsh/functional/src/*
    +X each Y
    +X filter Y
    +X fold Y
    +X map Y
    +
    +$ mapa '$1*2' {1..3}
    +2
    +4
    +6
    +
    +$ mapl 'echo result $1' $(mapa '$1+5' $(mapa '$1*2' {1..3}))
    +result 7
    +result 9
    +result 11
    +
    +

    tlpl: des fonctions d’ordres supérieurs en zsh.

    +
    + +

    Tout d’abord, pourquoi c’est important d’avoir ces fonctions. Plus je programmais avec zsh plus j’essayais d’avoir un style fonctionnel.

    +

    Le minimum pour pouvoir avoir du code plus lisible c’est de posséder les fonctions map, filter et fold.

    +

    Voici pourquoi avec une comparaison. Commençons par un programme qui converti tous les gif en png dans plusieurs répertoires projets contenant tous des répertoires resources. Avant :

    +

    Avant ⇒

    +
    # for each directory in projects dir
    +for toProject in /path/to/projects/*(/N); do
    +    # toProject is /path/to/projects/foo
    +    # project become foo (:t for tail)
    +    project=${toProject:t}
    +    for toResource in $toProject/resources/*.gif(.N); do
    +        convert $toResource ${toResource:r}.png && \
    +        \rm -f $toResource
    +    done
    +done
    +
      +
    • Le (/N) permet de sélectionner seulement les répertoires sans casser la boucle s’il n’y a pas de “match”.
    • +
    • Le (.N) permet de sélection seulement les fichiers, aussi sans tout arréter s’il ne trouve rien.
    • +
    • Le :t signfie “tail” ; si toto=/path/to/file.ext alors ${toto:t}=file.ext.
    • +
    +

    Après

    +
    gif_to_png() { convert $1 ${1:r}.png && \rm -f $1 }
    +
    +handle_resources() { map gif_to_png $1/resources/*.gif(.N) }
    +
    +map handle_resources /path/to/projects/*(/N)
    +

    Plus de bloc ! Oui, c’est un poil plus difficile à lire pour les non initiés. Mais c’est à la fois plus concis et plus robuste.

    +

    Et encore ce code ne possède pas de test. Recommençons sur le même principe.

    +

    Trouver les fichiers des projets qui ne contiennent pas de s dans leur nom qui ont le même nom que leur projet.

    +

    Before ⇒

    +
    for toProject in Projects/*; do
    +    project=$toProject:t
    +    if print -- project | grep -v s >/dev/null
    +    then
    +        print $project
    +        for toResource in $toProject/*(.N); do
    +            if print -- ${toResource:t} | grep $project >/dev/null; then
    +                print -- "X $toResource"
    +            fi
    +        done
    +    fi
    +done
    +

    After ⇒

    +
    contain_no_s() { print $1 | grep -v s }
    +
    +function verify_file_name {                               
    +    local project=$1:t
    +    contains_project_name() { print $1:t | grep $project }
    +    map "print -- X" $(filter contains_project_name $1/*(.N))
    +}
    +
    +map verify_file_name $( filter contain_no_s Projects/* )
    +

    La première version peu paraître plus facile à lire. Mais la seconde est plus bien supérieure en terme d’architecture. Je ne veux pas discuster ici pourquoi c’est mieux. Je vous demande simplement de me croire quand je dis que l’approche fonctionnelle est supérieure.

    +

    Vous pouvez télécharger une version à jour du code (merci à Arash Rouhani). Une ancienne version est ici. Voici le code source (de la première version) :

    +
    #!/usr/bin/env zsh
    +
    +# Provide higer-order functions 
    +
    +# usage:
    +#
    +# $ foo(){print "x: $1"}
    +# $ map foo a b c d
    +# x: a
    +# x: b
    +# x: c
    +# x: d
    +function map {
    +    local func_name=$1
    +    shift
    +    for elem in $@; print -- $(eval $func_name $elem)
    +}
    +
    +# $ bar() { print $(($1 + $2)) }
    +# $ fold bar 0 1 2 3 4 5
    +# 15
    +# -- but also
    +# $ fold bar 0 $( seq 1 100 )
    +function fold {
    +    if (($#<2)) {
    +        print -- "ERROR fold use at least 2 arguments" >&2
    +        return 1
    +    }
    +    if (($#<3)) {
    +        print -- $2
    +        return 0
    +    } else {
    +        local acc
    +        local right
    +        local func_name=$1
    +        local init_value=$2
    +        local first_value=$3
    +        shift 3
    +        right=$( fold $func_name $init_value $@ )
    +        acc=$( eval "$func_name $first_value $right" )
    +        print -- $acc
    +        return 0
    +    }
    +}
    +
    +# usage:
    +#
    +# $ baz() { print $1 | grep baz }
    +# $ filter baz titi bazaar biz
    +# bazaar
    +function filter {
    +    local predicate=$1
    +    local result
    +    typeset -a result
    +    shift
    +    for elem in $@; do
    +        if eval $predicate $elem >/dev/null; then
    +            result=( $result $elem )
    +        fi
    +    done
    +    print $result
    +}
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-09-28 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/Learn-Vim-Progressively/index.html b/Scratch/fr/blog/Learn-Vim-Progressively/index.html new file mode 100644 index 0000000..10d9200 --- /dev/null +++ b/Scratch/fr/blog/Learn-Vim-Progressively/index.html @@ -0,0 +1,377 @@ + + + + + + YBlog - Apprenez Vim Progressivement + + + + + + + + + + + + + +
    + + +
    +

    Apprenez Vim Progressivement

    +
    +
    +
    +
    +

    Über leet use vim!

    +
    + +

    tlpl: Vous désirez apprendre vim (le meilleur editeur de texte connu à ce jour) le plus rapidement possible. Voici mes conseils pour vous aider. Commencez à apprendre le minimum vital, puis apprenez doucement de nouvelles commandes.

    +
    + +

    Vim ou l’éditeur qui vallait 3 milliards :

    +
    +

    Meilleur, plus fort, plus rapide.

    +
    +

    Apprenez vim et ce sera votre dernier éditeur. Aucun éditeur que je connaisse ne le surpasse. Sa prise en mais est difficile, mais payante.

    +

    Je vous conseille de l’apprendre en 4 étapes :

    +
      +
    1. La survie
    2. +
    3. Se sentir à son aise
    4. +
    5. Se sentir meilleur, plus fort et plus rapide
    6. +
    7. Tirer parti des super-pouvoirs de vim
    8. +
    +

    À la fin de ces leçons vous serez transformé.

    +

    Avant de commencer, un message pour vous prévenir. Apprendre vim sera difficile au début. Ça prendra du temps. Vous devrez vous entraîner. Apprendre vim ressemble beaucoup à apprendre un instrument de musique. N’espérez pas être plus efficace avec vim qu’avec un autre éditeur avant au moins trois jours. En fait ça sera certainement plus 2 semaines que 3 jours.

    +

    1er Niveau – Survivre

    +
      +
    1. Installez vim
    2. +
    3. Lancez vim
    4. +
    5. NE TOUCHEZ A RIEN! Lisez
    6. +
    +

    Dans un éditeur normal, il suffit de taper sur une touche du clavier et la lettre s’affiche à l’écran. Pas ici. Vim est en mode Normal. Commençons par placer vim en mode Insert. Tapez sur la touche i.

    +

    Voilà, c’est magique. Vous pouvez tapez comme dans un éditeur standard. Pour repasser en mode Normal tapez sur la touche Echap.

    +

    Maintenant que vous savez passer du mode Normal au mode Insert. Voici les commandes de survie (toutes en mode Normal) :

    +
    +
      +
    • i → Passer en mode insértion. Taper Echap pour repasser en mode Normal.
    • +
    • x → Supprimer le caractère sous le curseur
    • +
    • :wq → Sauvegarder et quitter (:w sauvegarde, :q<enter> quitter)
    • +
    • dd → Supprimer (et copier) la ligne courante
    • +
    • p → Coller
    • +
    +

    Récommandées :

    +
      +
    • hjkl (optionnel) → se déplacer (<-↓↑→). Souvenez vous j ressemble à une flèche vers le bas.
    • +
    • :help <commande> → Affiche l’aide pour <commande>. Vous pouvez aussi écrire :help pour atterir sur l’aide générale.
    • +
    +
    +

    Seulement 5 commandes. Voilà, c’est tout pour un début. Essayez d’éditer vos fichiers comme ça pendant une petite journée. Lorsque ces commandes vous sembleront naturelles, vous pourrez passer à l’étape d’après.

    +

    Mais avant un petit mot sur le mode Normal. Dans un éditeur normal pour copier il faut utiliser une combinaison de touches (Ctrl-c). En fait, lorsque vous appuyez sur la touche Ctrl, c’est un peu comme si toutes les touches du clavier avaient un autre usage. Dans vim, lorsque vous êtes en mode Normal, c’est comme si vous mainteniez Ctrl enfoncé.

    +

    Quelques mots concernant les notations :

    +
      +
    • Au lieu d’écrire Ctrl-λ, j’écrirai <C-λ>.
    • +
    • Les commandes qui commencent par : ont un retour à la ligne implicite à la fin. Par exemple lorsque que j’écris, :q celà signifi qu’il faut taper :, suivi de q, suivi de <Return>.
    • +
    +

    2ème Niveau – Se sentir à son aise

    +

    Vous connaissez les commandes de survie. Passons à des commandes pour être un peu plus à notre aise. Je vous suggère :

    +
      +
    1. Les variantes de l’insertion

      +
      +
        +
      • a → Comme i, mais après la position du curseur.
      • +
      • o → Comme i, mais à la ligne suivante.
      • +
      • O → Comme o mais ajoute la ligne avant.
      • +
      • cw → Remplacer la fin du mot.
      • +
      +
    2. +
    3. Déplacements basiques

      +
      +
        +
      • 0 → Aller à la première colonne.
      • +
      • ^ → Aller au premier caractère de la ligne.
      • +
      • $ → Aller à la fin de la ligne.
      • +
      • g_ → Aller au dernier caractère de la ligne.
      • +
      • /pattern → Rechercher pattern dans le fichier.
      • +
      +
    4. +
    5. Copier/Coller

      +
      +
        +
      • P → Coller avant. Souvenez vous, p colle après la position du curseur.
      • +
      • yy → Copier la ligne courante. C’est plus simple et équivalent à ddP
      • +
      +
    6. +
    7. Annuler/Refaire

      +
      +
        +
      • u → Annuler (undo)
      • +
      • <C-r> → Refaire
      • +
      +
    8. +
    9. Ouvrir/Sauvegarder/Quitter/Changer de fichier (buffer)

      +
      +
        +
      • :e <path/to/file> → Ouvrir.
      • +
      • :w → Sauvegarder.
      • +
      • :saveas <path/to/file> → Sauvegarder sous …
      • +
      • :x, ZZ ou :wq → Sauvegarder et quitter (:x sauvegarde seulement si nécessaire).
      • +
      • :q! → Quitter sans sauvegarder. De même :qa! quitte même si d’autres fichiers (buffers) ont des modifications non sauvegardées.
      • +
      • :bn (resp. :bp) → Affiche le fichier suivant (resp. précédent).
      • +
      +
    10. +
    +

    Prenez le temps de bien intégrer ces commandes. Une fois fait, vous devriez être capable de faire tout ce qu’on peut attendre d’un éditeur de texte classique.

    +

    3ième Niveau – Meilleur. Plus fort. Plus rapide.

    +

    Bravo ! Si vous êtes arrivé jusqu’ici nous allons pouvoir commencer à apprendre les choses vraiment intéressantes. Pour cette section, je vais seulement parler de commandes disponible dans vi et vim. Vim est la contraction de “vi improved”, ou en Français, “vi amélioré”.

    +

    Meilleur

    +

    Voyons comment nous pouvons éviter les répétitions avec vi :

    +
      +
    1. . → Le caractère point répètera la dernière commande. Très utile.
    2. +
    3. N<commande> → répètera la commande N fois.
    4. +
    +

    Quelques exemples, ouvrez un fichier (non vide) avec vim et tapez :

    +
    +
      +
    • 2dd → Supprimera 2 lignes
    • +
    • 3p → copiera 3 fois d’affiler le texte copié
    • +
    • 100idesu [ESC] → écrira “desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu”
    • +
    • . → Juste après la dernière commande réécrira les 100 “desu”.
    • +
    • 3. → Écrira 3 “desu” et non pas 300. Bien vu n’est-ce pas ?
    • +
    +
    +

    Plus fort

    +

    Savoir se déplacer efficacement avec vim est très important. Ne sautez pas cette section.

    +
      +
    1. NG → Aller à la ligne N
    2. +
    3. gg → raccourci pour 1G, retourner au début du fichier
    4. +
    5. G → Aller à la dernière ligne.
    6. +
    7. Déplacement autour des mots:

      +
      +
        +
      1. w → aller au début du mot suivant
      2. +
      3. e → aller à la fin du mot courant
      4. +
      +

      Par défaut les mots sont seulement composés de lettres (et du caractère souligné _). Appelons un MOT un ensemble de lettre séparé par des caractères blancs (espaces, tabulation). Si vous voulez considérer des MOTS alors il suffit d’utiliser les majuscules.

      +
        +
      1. W → aller au début du MOT suivant
      2. +
      3. E → aller à la fin du MOT courant
      4. +
      +

      Word moves example

      +
    8. +
    +

    Passons aux commandes de déplacement les plus efficaces :

    +
    +
      +
    • % : Aller à la parenthèse, accolade, crochet correspondante.
    • +
    • * (resp. #) : Aller à la prochaine (resp. précédente) occurrence du mot sous le curseur
    • +
    +
    +

    Croyez moi, ces trois dernières commandes valent de l’or. Retenez les et vous gagnerez beaucoup de temps.

    +

    Plus rapide

    +

    Vous vous souvenez que j’ai dit que les déplacements étaient très importants en vi. Voilà pourquoi. Une façon de travailler avec vim est de se dire que l’on fait des “phrases”. Le verbe étant la commande et les compléments définissent la zone d’action. De façon générale :

    +

    <position de depart><commande><position d'arrivee>

    +

    Par exemple : 0y$ signifie :

    +
      +
    • 0 → Aller au début de la ligne,
    • +
    • y → copie à partir d’ici,
    • +
    • $ → jusqu’à la fin de cette ligne.
    • +
    +

    On peut donc faire des choses comme ye, copie à partir de la position courante du curseur jusqu’à là fin du mot. Mais aussi: y2/toto copie jusqu’à la seconde prochaine occurrence de “toto”.

    +

    Ce qui est vrai pour y (yank → copier), est aussi vrai pour d (delete → supprimer), v (sélection visuelle), gU (uppercase → majuscule),gu (lowercase → minuscule), etc…

    +

    4ième Niveau – Les super pouvoirs de Vim

    +

    Jusqu’ici vous avez appris les commandes les plus courantes. Mais voici les killer features de vim. Celles que je n’ai retrouvé que dans vim (ou presque).

    +

    Déplacement sur la ligne : 0 ^ $ g_ f F t T , ;

    +
    +
      +
    • 0 → aller à la colonne 0,
    • +
    • ^ → aller au premier caractère de la ligne
    • +
    • $ → aller à la dernière colonne de la ligne
    • +
    • g_ → aller au dernier caractère de la ligne
    • +
    • fa → vous amène à la prochaine occurrence de a sur la ligne courante. , (resp. ;) recherche l’occurrence suivante (resp. précédente).
    • +
    • t, → vous amène juste avant le ,.
    • +
    • 3fa → recherche la 3ième occurrence de a.
    • +
    • F et T → comme f et t mais en arrière. Line moves
    • +
    +
    +

    Un truc pratique : dt" → supprime tout jusqu’au prochain ".

    +

    Selection de zone <action>a<object> ou <action>i<object>

    +

    Ces commandes sont utilisable seulement en mode visuel ou après un “opérateur”. Mais elles sont très puissantes. Leur forme générale est:

    +

    <action>a<objet> et <action>i<objet>

    +

    Où action peut être par exemple d (delete), y (yank), v (select in visual mode), etc… Un objet peut être: w un mot, W un MOT (mot étendu), s une phrase, p un paragraphe. Mais aussi des caractère plus naturels comme ", ', ), }, ].

    +

    Supposons que le curseur soit positionné sur le premier o dans (map (+) ("foo")).

    +
    +
      +
    • vi" → sélectionnera foo.
    • +
    • va" → sélectionnera "foo".
    • +
    • vi) → sélectionnera "foo".
    • +
    • va) → sélectionnera ("foo").
    • +
    • v2i) → sélectionnera map (+) ("foo")
    • +
    • v2a) → sélectionnera (map (+) ("foo"))
    • +
    +
    +

    Text objects selection

    +

    Sélection de blocs rectangulaires : <C-V>.

    +

    Les blocs rectangulaires sont très commodes pour commenter plusieurs lignes de codes. Typiquement: ^<C-V><C-d>I-- [ESC]

    +
      +
    • ^ → aller au premier caractère de la ligne
    • +
    • <C-V> → Commencer la sélection du bloc
    • +
    • <C-d> → se déplacer vers le bas (pourrait être jjj ou % etc…)
    • +
    • I-- [ESC] → écrit -- pour commenter le reste de la ligne.
    • +
    +

    Rectangular blocks

    +

    Remarquez que sous windows, vous devez utiliser <C-q> plutôt que <C-v> si votre “presse papier” n’est pas vide.

    +

    Complétion : <C-n> et <C-p>.

    +

    En mode Insert, commencez à écrire le début d’un mot déjà présent dans l’un des buffers (fichers) ouvert et tapes <C-p>. Magique. Completion

    +

    Macros : qa faire quelque chose q, @a, @@

    +

    qa enregistre tout ce que vous faite et enregistre le tout dans le registre a. Ensuite @a va rejouer la macro enregistrée dans le registre a comme si c’est vous qui tapiez au clavier. @@ est un raccourci pour rejouer la dernière macro exécutée.

    +
    +

    Exemple : Sur une ligne contenant seulement un 1 tapez :

    +
      +
    • qaYp<C-a>q

    • +
    • qa → début de l’enregistrement.
    • +
    • Yp → copier cette ligne.
    • +
    • <C-a> → incrémente le nombre.
    • +
    • q → arrête d’enregistrer.

    • +
    • @a → écrit un 2 sous le 1.
    • +
    • Écrivez 100@@. Cela va créer une liste de nombre croissants jusqu’à 103.

    • +
    +
    +

    Macros

    +

    Sélection visuelle : v,V,<C-v>

    +

    On a déjà vu un exemple avec <C-V>. Mais il y a aussi, v et V. Et une fois la sélection visuelle faite vous pouvez par exemple:

    +
      +
    • J → joindre toutes les lignes pour en faire une seule
    • +
    • < (resp. >) → indenter à gauche (resp. à droite).
    • +
    • = → auto indenter
    • +
    +

    Autoindent

    +

    Ajouter quelque chose à la fin de toutes les lignes sélectionnées visuellement :

    +
      +
    • <C-v>
    • +
    • aller jusqu’à la ligne désirée (jjj ou <C-d> ou /pattern ou % etc…)
    • +
    • $ aller à la fin
    • +
    • A, écrire le texte, Echap.
    • +
    +

    Ajouter à la fin de plusieurs lignes

    +

    Splits : :split et vsplit.

    +

    Je vous conseille de faire un :help split. Celà permet de manipuler plusieurs buffer sur la même fenêtre. Voici les commandes principales :

    +
    +
      +
    • :split → crée un split (:vsplit crée un split vertical)
    • +
    • <C-w><dir> → où dir est l’un de hjkl ou ←↓↑→ permet de changer de split.
    • +
    • <C-w>_ (resp. <C-w>|) → Maximise la taille du split (resp. split vertical)
    • +
    • <C-w>+ (resp. <C-w>-) → Agrandi (resp. diminue) le split
    • +
    +
    +

    Split

    +

    Conclusion

    +

    Voilà, je vous ai donné 90% des commandes que j’utilise tous les jours. N’essayez pas de toutes les apprendre en une journée. Il faut le temps de s’habituer à chaque nouvelle commande. Je vous conseille de ne pas apprendre plus d’une ou deux commandes par jour.

    +

    Apprendre Vim est plus une question d’entraînement que de mémorisation. Heureusement vim est founi avec un très bon tutoriel et une excellente documentation. Lancez vimtutor jusqu’à ce que vous vous sentiez à l’aise avec les commandes basiques. De plus, vous devriez aussi lire en détail la page suivate : :help usr_02.txt.

    +

    Ensuite vous découvrirez !, les folds, les registres, les plugins et tout un tas d’autres choses. Apprenez vim comme vous apprendriez le piano et vous devriez très bien vous en sortir.

    + + + +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-08-25 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/Password-Management/index.html b/Scratch/fr/blog/Password-Management/index.html new file mode 100644 index 0000000..8692671 --- /dev/null +++ b/Scratch/fr/blog/Password-Management/index.html @@ -0,0 +1,181 @@ + + + + + + YBlog - Password Management + + + + + + + + + + + + + +
    + + +
    +

    Password Management

    +
    +
    +
    +
    +

    Title image

    +
    + +

    tlpl: Une méthode de gestion des mots de passes que j’utilise avec succès depuis quelques années.
    sha1( mot_de_passe + nom_de_domaine )
    Je ne mémorise qu’un seul mot de passe de très bonne qualité. J’utilise des mots de passe différents sur tous les sites.

    +
    + +

    Avant de commencer, je tiens à préciser qu’il s’agit d’une tentative de vous vendre mon appli iPhone ;-).

    +

    Vous êtes toujours là ? Bon, d’accord, même si vous ne téléchargez pas mon application vous pouvez quand même utiliser ma méthode. Elle est à la fois très sûre et simple à utiliser.

    +

    Si vous souhaitez simplement utiliser le système sans essayer de comprendre comment ça marche derrière vous pouvez aller à la fin de cet article en cliquant ici.

    +

    Pourquoi devriez-vous utiliser un gestionnaire de mot de passe ?

    +
    +

    Même les paranoïaques peuvent avoir des ennemis.

    +
    +

    Imaginez que vous trouviez un très bon mot de passe. Vous l’utilisez sur gmail, amazon, PayPal, twitter, facebook… Plus tard, vous découvrez un super petit jeu en ligne très sympa. Vous devez vous enregistrer pour y jouer. Le site vous demande votre email et un mot de passe. Quelques semaines/mois se passent. La machine qui héberge le jeu en ligne se fait attaquer. Maintenant, l’attaquant du site web possède votre email avec ce mot de passe. Il peut donc essayer votre mot de passe un peu partout. Sur PayPal par exemple.

    +

    Bien, maintenant comment pouvons nous régler ce problèmes ?

    +

    Quelle méthodologie ?

    +
    +

    Le bon, la brute et le truand

    +
    +

    La méthode la plus courante est de se souvenir de plusieurs mot de passes différents. En général, si vous avez bonne mémoire vous pouvez mémoriser jusqu’à 13 mots de passes. Certain de bonne qualité, d’autre moins.

    +

    Que faire si vous utilisez plus de services que vous pouvez mémoriser de mots de passe ?

    +

    Un mauvaise solution peut être de choisir ses mots de passes de la façon suivante :

    +
      +
    • twitter: P45sW0r|)Twitter
    • +
    • gmail: P45sW0r|)gmail
    • +
    • badonlinegame: P45sW0r|)badonlinegame
    • +
    +

    Malheureusement, si quelqu’un récupère votre mot de passe sur badonlinegame, il peut facilement retrouvez vos autres mots de passe. Bien sûr, on peut imaginer des transformation de mots de passe de meilleure qualité. Mais il est très difficile d’en trouver une suffisamment bonne.

    +

    Fort heureusement, il existe une des fonctions bien connues dans le milieu de la sécurité informatique et qui résolvent précisément ce problème. Il s’agit des fontions de hachages. Il est difficile de retrouver le paramètre d’entrée d’une fonction de hachage à partir de son résultat. Prenons un exemple :

    +

    Si quelqu’un possède 9f00fd5dbba232b7c03afd2b62b5fce5cdc7df63, il va avoir de grande difficulté pour retrouver P45sW0r|).

    +

    Choisisson la fonction de hashage sha1. Connaissant celà, le mot de passe d’un site donné doit avoir la forme :

    +

    Où :

    +
      +
    • master_password est votre unique mot de passe maître ;
    • +
    • domain_name est le nom de domaine du site pour lequel vous voulez le mot de passe.
    • +
    +
    +

    Il faut aussi penser à certaines contraintes. Certains site web veulent des mots de passe d’une certaine longueur, ni trop longs ni trop courts. Que faire si vous voulez changez votre mot de passe ? Soit parce qu’il est compromis ou simplement parce qu’on vous impose de le changer. C’est pouquoi pour chaque site on a besoin de quelques paramètres supplémentaires.

    +
      +
    • le nom de login ;
    • +
    • la longueur du mot de passe ;
    • +
    • le numéro du mot de passe (pour le changer au cas où) ;
    • +
    • le format du mot de passe : hexadécimal ou base64.
    • +
    +

    En pratique ?

    +

    Selon ma situation, voici les outils que j’ai fait et que j’utilise :

    +
      +
    • Sur mon Mac :
    • +
    • J’utilise le widget YPassword
    • +
    • Parfois, certains champs de mots passe interdisent la copie. Dans ce cas, j’utilise un petit utilitaire en AppleScript : ForcePaste.
    • +
    • Sous mon Linux : J’utilise le script ypassword
    • +
    • Sur mon iPhone : J’utilise l’application YPassword
    • +
    • Sur tous les autres ordinateurs :
    • +
    • L’application Web Cappuccino YPassword
    • +
    • L’application Web jQuery YPassword
    • +
    +

    Quelquesoit mon environnement de travail, tous mes mots de passes sont à un copier/coller. Pour certain services, j’utilise des mots de passe de 40 caractères. Actuellement j’utilise plutôt des mots de passes de 10 caractères. Avec des mots de passes plus petit, il est encore plus difficile pour un attaquant de retrouver mon mot de passe principal.

    +

    Je serai heureux de savoir ce que vous pensez de cette méthode. Alors n’hésitez pas à laisser un commentaire ou à m’envoyer un mail.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-05-18 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/SVG-and-m4-fractals/index.html b/Scratch/fr/blog/SVG-and-m4-fractals/index.html new file mode 100644 index 0000000..b955275 --- /dev/null +++ b/Scratch/fr/blog/SVG-and-m4-fractals/index.html @@ -0,0 +1,242 @@ + + + + + + YBlog - Accroître le pouvoir des languages déficients. + + + + + + + + + + + + + +
    + + +
    +

    Accroître le pouvoir des languages déficients.

    +
    +
    +
    +
    +

    Yesod logo made in SVG and m4

    +
    + +

    tlpl: Utiliser m4 pour accroître le pouvoir d’xslt et d’svg. Example cool, les fractales.

    +
    + +

    Lorsqu’xml fût inventé beaucoup pensaient que c’était l’avenir. Passer de fichiers plat à des fichiers structurés standardisés fût un grand progrès dans beaucoup de domaines. Cerain se mirent à voir du xml de partout. À tel point que les les format compatibles xml naquirent de toute part. Non seulement comme format de fichier, mais aussi comme format pour un langage de programmation.

    +

    Ô joie !

    +

    Malheureusement, xml fût fabriquer pour le transfert de données. Pas du tout pour être vu ou édité directement. La triste vérité est qu’xml est verbeux et laid. Dans un monde parfait, nous ne devrions avoir des programmes qui s’occupent de nous afficher correctement le xml pour nous épargner la peine de les voir directement. Mais devinez quoi ? Notre monde n’est pas parfait. Beaucoup de programmeurs sont ainsi forcé de travailler directement avec de l’xml.

    +

    xml, n’est pas le seul cas de format mal utilisé que je connaisse. Vous avez d’autres formats dans lesquels il serait très agréable d’ajouter des variables, des boucles, des fonctions…

    +

    Mais je suis là pour vous aider. Si comme moi vous détestez xslt ou écrire de l’xml. Je vais vous montrer une façon d’améliorer tout ça.

    +

    Un exemple avec xslt

    +

    Commençons avec le pire cas de langage xml que je connaisse : xslt. Tous les développeurs qui ont déjà dû écrire du xslt savent à quel point ce langage est horrible.

    +

    Pour réduire la “verbosité” de tels langages, il y a un moyen. m4. Oui, le préprocesseur utilisé par C et C++.

    +

    Voici certains exemples :

    +
      +
    • Les variables, au lieu d’écrire myvar = value, voici la version xslt :
    • +
    +
    <xsl:variable name="myvar" select="value"/>
    +
      +
    • Afficher quelquechose. Au lieu de print "Hello world!", xslt nous offre :
    • +
    +
    <xsl:text 
    +    disable-output-escaping="yes"><![CDATA[Hello world!
    +]]></xsl:text>
    +
      +
    • afficher la valeur d’une variable, au lieu de print myvar, nous avons droit à :
    • +
    +
    <xslt:value-of select="myvar"/>
    +
      +
    • Essayez d’imaginer à quel point il est verbeux de déclarer une fonction dans ce langage.
    • +
    +

    La solution (m4 à la rescousse)

    +
    <?xml version="1.0" standalone="yes"?> <!-- YES its <span class="sc">xml</span> -->
    +<!-- ← start a comment, then write some m4 directives:
    +
    +define(`ydef',`<xsl:variable name="$1" select="$2"/>')
    +define(`yprint',`<xsl:text disable-output-escaping="yes"><![CDATA[$1]]></xsl:text>')
    +define(`yshow',`<xsl:value-of select="$1"/>')
    +
    +-->
    +<!-- Yes, <span class="sc">xml</span> sucks to be read -->
    +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    +<!-- And it sucks even more to edit -->
    +<xsl:template match="/">
    +    ydef(myvar,value)
    +    yprint(Hello world!)
    +    yshow(myvar)
    +</xsl:template>
    +

    Maintenant compilons simplement ce fichier :

    +
    m4 myfile.m4 > myfile.xslt
    +

    Et vous pouvez profitez ! Maintenant xslt devient plus lisible et plus facile à éditer.

    +

    La partie la plus cool: les fractales !

    +

    À ses débuts, beaucoup pensaient que ce serait le nouveau Flash. Apparemment, ce devrait plutôt être canvas avec du javascript qui occupera cette place.

    +

    Tout d’abord, laissez moi vous montrer le résultat :

    +

    Yesod logo made in SVG and m4 Cliquez sur l’image pour voir le svg directement. Attention, si vous n’avez pas un ordinateur récent, ça risque de ramer.

    +

    Le positionnement du texte “esod” par rapport au “λ” renversé a été en jouant avec firebug. De cette façon je n’avais pas à regénérer pour tester.

    +

    Faire une telle fractale revient à :

    +
      +
    1. Choisir un élément racine ;
    2. +
    3. le dupliquer et le transformer ;
    4. +
    5. le résultat est un nouveau sous-élément ;
    6. +
    7. répéter à partir de 2 mais en utilisant le sous-élément comme nouvelle racine.
    8. +
    9. Arréter lorsque la récursion est assez profonde.
    10. +
    +

    Si j’avais dû faire ça manuellement, il m’aurait fallu faire beaucoup de copier/coller dans mon svg. Simplement parce que la transformation est toujours la même, mais je ne pouvais pas dire, utiliser la transformation appelée “titi”. Plutôt que copier du xml, j’ai utilisé m4.

    +

    Et voici le code commenté :

    +
    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    +<!--
    +     M4 Macros
    +define(`YTRANSFORMONE', `scale(.43) translate(-120,-69) rotate(-10)')
    +define(`YTRANSFORMTWO', `scale(.43) translate(-9,-67.5) rotate(10)')
    +define(`YTRANSFORMTHREE', `scale(.43) translate(53,41) rotate(120)')
    +define(`YGENTRANSFORM', `translate(364,274) scale(3)')
    +define(`YTRANSCOMPLETE', `
    +    <g id="level_$1">
    +        <use style="opacity: .8" transform="YTRANSFORMONE" xlink:href="#level_$2" />
    +        <use style="opacity: .8" transform="YTRANSFORMTWO" xlink:href="#level_$2" />
    +        <use style="opacity: .8" transform="YTRANSFORMTHREE" xlink:href="#level_$2" />
    +    </g>
    +    <use transform="YGENTRANSFORM" xlink:href="#level_$1" />
    +')
    + -->
    +<svg 
    +    xmlns="http://www.w3.org/2000/svg" 
    +    xmlns:xlink="http://www.w3.org/1999/xlink"
    +    x="64" y="64" width="512" height="512" viewBox="64 64 512 512"
    +    id="svg2" version="1.1">
    +    <g id="level_0"> <!-- some group, if I want to add other elements -->
    +        <!-- the text "λ" -->
    +        <text id="lambda" 
    +            fill="#333" style="font-family:Ubuntu; font-size: 100px"
    +            transform="rotate(180)">λ</text>
    +    </g>
    +    <!-- the text "esod" -->
    +    <text 
    +        fill="#333" 
    +        style="font-family:Ubuntu; font-size: 28px; letter-spacing: -0.10em" 
    +        x="-17.3" 
    +        y="69" 
    +        transform="YGENTRANSFORM">esod</text>
    +    <!-- ROOT ELEMENT -->
    +    <use transform="YGENTRANSFORM" xlink:href="#level_0" />
    +
    +    YTRANSCOMPLETE(1,0) <!-- First recursion -->
    +    YTRANSCOMPLETE(2,1) <!-- deeper -->
    +    YTRANSCOMPLETE(3,2) <!-- deeper -->
    +    YTRANSCOMPLETE(4,3) <!-- even deeper -->
    +    YTRANSCOMPLETE(5,4) <!-- Five level seems enough -->
    +</svg>
    +

    et je l’ai compile en svg et ensuite en png avec :

    +
    m4 yesodlogo.m4 > yesodlogo.svg && convert yesodlogo.svg yesodlogo.png
    +

    Le λ est dupliqué avec trois “transformations” différentes. Les transformations sont : YTRANSFORMONE, YTRANSFORMTWO et YTRANSFORMTHREE.

    +

    Chaque transformation est une similarité (translation + rotation + zoom, ce qui est équivalent à juste rotation + zoom, mais bon).

    +

    Une fois fixée chaque transformation peut ensuite être réutilisée pour chaque nouveau niveau.

    +

    Maintenant YTRANSCOMPLETE entre en jeu. Cette macro prend deux arguments. Le niveau courant et le niveau précédent. Cette macro va dupliquer le niveau précédent en lui appliquant chacune des 3 transformations. Au niveau 0, le contenu est un seul grand λ, le niveau 1 en contient 3. Le niveau 2 en contient 9, etc… Le niveau 5 contient 35=243 λ. Tous les niveaux combinés représentent 36-1 / 2 = 364 λ.

    +

    L’avantage principal c’est que je pouvais visualiser le résultat final facilement. Sans ce système de macro, pour faire une preview il m’aurait fallu faire des copier/coller + quelques modifications à chaque essai.

    +

    Conclusion

    +

    Ce fut très amusant de faire une fractale en svg, mais la partie la plus intéressante était d’augmenter la puissance d’expressivité du langage en utilise un préprocesseur. J’ai utilisé cette méthode avec xslt pour une vrai application par exemple. On peut aussi utiliser m4 pour faire des includes d’autres fichiers. Typiquement je l’ai utiliser pour les includes dans un format obscur. Mais vous pouvez aussi le considérer pour des includes dans du HTML. Par exemple pour fabriquer un site statique rapidement, m4 peut se révéler utile pour inclure un footer ou un menu sur toutes les pages par exemple. J’ai aussi pensé que l’on pouvait utiliser m4 pour structurer des programmes comme brainfuck.

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-10-20 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/Typography-and-the-Web/index.html b/Scratch/fr/blog/Typography-and-the-Web/index.html new file mode 100644 index 0000000..f02364b --- /dev/null +++ b/Scratch/fr/blog/Typography-and-the-Web/index.html @@ -0,0 +1,179 @@ + + + + + + YBlog - La typography et le Web + + + + + + + + + + + + + +
    + + +
    +

    La typography et le Web

    +
    +
    +
    +
    +

    +
    + +

    tlpl: La typography sur le web est pourrie et nous ne somme pas près de voir ce problème réparé.

    +
    + +

    Je suis tombé sur ce site: open typography. Leur message principal est :

    +
    +

    «There is no reason to wait for browser development to catch up. We can all create better web typography ourselves, today.»

    +
    +

    ou en français :

    +
    +

    «Nous ne somme pas obligé d’attendre le développement des navigateurs. Nous pouvons créer un web avec une meilleure typographie aujourd’hui.»

    +
    +

    Comme quelqu’un qui a déjà essayé d’améliorer la typographie de son site web, et en particulier des ligatures, je crois que c’est faux.

    +

    J’ai déjà écrit un système automatique qui détecte et ajoute des ligatures en utilisant des caractères unicode. Cependant je n’ai jamais publié cette amélioration sur le web et voilà pourquoi :

    +

    Tout d’abord, qu’est-ce qu’un ligature ?

    +

    +

    Quel est le problème des ligatures sur le web ? Le premier c’est que vous ne pouvez pas chercher les mots qui contiennent ces ligatures. Par exemple essayez de chercher le mot “first”.

    +
      +
    • first ← Pas de ligature, pas de problème1.
    • +
    • r ← Une jolie ligature, mais introuvable avec une recherche (C-f).
    • +
    +

    Le second problème est le rendu. Par exemple, essayer d’utiliser un charactère de ligature en petites capitales :

    +
      +
    • first
    • +
    • r
    • +
    +

    Voici une capture d’écran pour que vous voyez ce que je vois :

    +

    +

    Le navigateur est incapable de comprendre que le caractère de ligature “” doit être rendu comme fi lorsqu’il est en petites capitales. Et une part du problème est que l’on peut décider d’écrire en petite majuscule dans le css.

    +

    Comment par exemple utiliser un charactère de ligature unicode sur un site qui possède différents rendus via différentes css ?

    +

    Comparons à LaTeX

    +

    +

    Si vous faites attention au détail, vous constaterez que le premier “first” contient une ligature. Bien entendu la deuxième ligne est affichée correctement. Le code que j’ai utilisé pour avoir ce rendu est simplement :

    +
    \item first
    +\item {\sc first}
    +

    LaTeX a été suffisamment intelligent pour créer les ligatures si nécessaire.

    +

    La ligature “” est rare et n’est pas rendu par défaut par LaTeX. Si vous voulez voir des ligatures rares, vous pouvez utiliser XƎLaTeX:

    +

    XeLaTeX ligatures

    +

    J’ai copié cette image de l’excellent article de Dario Taraborelli.

    +

    Clairement il sera difficile aux navigateurs de corriger ces problèmes. Imaginez le nombre de petites exceptions.

    +
      +
    • Le texte est en petites capitales, je ne dois pas utiliser de ligatures.
    • +
    • Le mot courant contient un caractère de ligature, je ne dois pas chercher d’autre ligature dans ce mot.
    • +
    • La fonte n’a pas défini de caractère unicode pour la ligature, je ne dois pas l’utiliser.
    • +
    • Une commande javascript a modifé le CSS, je dois vérifier si je dois remplacer les ligatures par les deux caractères.
    • +
    • etc…
    • +
    +

    Dans tous les cas, si quelqu’un possède une solution je suis preneur !

    +
    +
    +
      +
    1. En réalité, vous devriez pouvoir voir une ligature. Maintenant j’utilise : text-rendering: optimizelegibility. Le rendu est correct parce que j’utilise une fonte correct, à savoir Computer Modern de Donald Knuth.

    2. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2012-02-02 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/Yesod-excellent-ideas/index.html b/Scratch/fr/blog/Yesod-excellent-ideas/index.html new file mode 100644 index 0000000..b5447bf --- /dev/null +++ b/Scratch/fr/blog/Yesod-excellent-ideas/index.html @@ -0,0 +1,195 @@ + + + + + + YBlog - Les idées de yesod + + + + + + + + + + + + + +
    + + +
    +

    Les idées de yesod

    +
    +
    +
    +
    +

    Title image

    +
    + +

    tlpl:

    +

    Cela fait un moment que je suis la progression du framework yesod. À mon humble avis on peut commencer à l’utiliser pour des applications sérieuses (comprendre en prod). Avant de vous dire pourquoi vous devriez aussi le considérer, je préfère vous parler de bonnes idées (parmi d’autres) introduites par yesod que je n’avais jamais vu ailleurs.

    +
    + +

    Types saufs

    +

    Commençons par une BD d’xkcd :

    +
    +SQL injection by a mom

    SQL injection by a mom

    +
    +

    Lorsque vous créez une application web, beaucoup de temps est passé à s’occuper de chaînes de caractères. Des chaînes de caractère pour les URL, le HTML, le Javascript, les CSS, les requêtes SQL, etc… Pour éviter des utilisation malicieuses vous devez protéger chaque chaîne de caractère entre chaque étape. Par exemple supposons que vous entriez comme nom :

    +
    Newton<script>alert("An apple fall")</script>
    +

    Sans une protection correcte, le message “An apple fall” sera affiché à chaque fois que quelqu’un essayera d’accéder au nom de cet utilisateur. Les “types saufs” sont le tonyglandil du web. A chaque chaine de caractère, on lui associe un “type”. A quoi sert cette chaîne de caractère ? Est-ce une URL ? Du javascript ? De l’HTML ? Entre chaque passage d’une représentation à une autre, un transformation is faite par défaut.

    +

    Yesod fait de son mieux pour typer les objets manipulés et ainsi il fera ce qu’il faut pour ne pas mettre du script dans une URL par exemple.

    +

    Go to the other page ~~~~~~

    +

    Comme AnotherPageR est une URL elle ne pourra contiendra pas (par défaut) de caractère dangereux comme par exemple :

    +
    falselink"><script> bad_code(); </script><a href="pipo
    +

    Les widgets

    +

    Les widgets de yesod sont différents des widgets Javascripts (ou java). Pour yesod un widget est un ensemble de morceaux d’appli web. Et si dans une page on veut utiliser plusieurs widgets, alors yesod s’occupe de tout. Des exemples de widgets (au sens yesod) sont :

    +
      +
    • Le «footer» d’une page web,
    • +
    • Le «header» d’une page web,
    • +
    • un bouton qui apparaît lorsque l’on «scrolle» vers le bas,
    • +
    • etc…
    • +
    +

    Pour chacun de ces widgets vous pourriez avoir besoin d’

    +
      +
    • un peu d’HTML,
    • +
    • un peu de CSS et
    • +
    • un peu de javascript.
    • +
    +

    Certain morceau doivent être placés dans le «header» de la page et d’autre dans le «body».

    +

    Vous pouvez déclarer un widget comme suit (je n’utilise pas la vrai syntaxe) :

    +
    htmlheader = ...
    +cssheader = ...
    +javascriptheader = ...
    +htmlbody = ...
    +

    La vraie syntaxe est :

    +
    toWidgetHeader cassiusFile "button.cassius"
    +toWidgetHeader juliusFile "button.julius"
    +toWidget       hamletFile "buttonTemplate.hamlet"
    +

    Veuillez aussi noté la convention Shakespearienne des noms. Encore une bonne raison d’utiliser yesod.

    +
      +
    • Cassius & Lucius pour le CSS (très similaire à SASS et SCSS)
    • +
    • Julius pour le javascript (notons qu’il existe aussi un CoffeeScript qui traîne dans les sources de yesod)
    • +
    • Hamlet pour l’HTML (similaire à haml)
    • +
    +

    Lorsque vous générez votre page, yesod se débrouille pour que tout fonctionne ensemble:

    +
    myBigWidget =  menuWidget >> contentWidget >> footerWidget
    +

    De plus, si vous utilisez 10 widgets avec un peu de CSS, yesod fabriquera un unique fichier CSS pour vous. Bien entendu si vous préférez avoir une dizaine de fichier CSS vous pouvez aussi le faire.

    +

    C’est juste génial !

    +

    Routage optimisé

    +

    Dans un système de routage standard (à la ruby on rails par exemple) vous avez pour chaque entrée un couple: regexp → handler

    +

    La seule façon de découvrir la bonne règle est d’essayer de matcher l’url demandée à chaque expression régulière.

    +

    Au lieu d’essayer chaque expression régulière, yesod regroupe et compile les routes pour les optimiser. Bien entendu pour pouvoir profiter de cet avantage au mieux, il ne faut pas que deux routes interfèrent entres elles.

    +
    /blog/2003  Date2003R
    +/blog/$DATE DateR
    +

    Cette définition de route est invalide par défaut dans yesod. Si vous voulez vraiment vous pouvez le faire foncionner quand même, mais il me semble que ça doit être quasiment toujours une mauvaise idée.

    +

    Il vaut mieux faire :

    +
    /blog/$DATE DateR
    +

    et faire le test “est-ce que date = 2003 ?” dans le «handler».

    +

    Pourquoi yesod?

    +
      +
    1. La vitesse. Simplement incroyable, je ne pense pas qu’il existe quelque chose de plus rapide aujourd’hui. Regardez d’abord cet article puis celui-ci.
    2. +
    3. Haskell. C’est certainement le langage de programmation le plus difficile à apprendre que j’ai jamais rencontré. Mais aussi l’un des plus incroyables. Si vous voulez rencontrer tout un tas de notions que vous n’avez jamais croisées avant et faire exploser votre cerveau avec de nouvelles idées, alors apprenez Haskell.
    4. +
    5. Bonnes idées et communauté excellente. Cela fait quelques mois que je suis la progression de yesod. Et la vitesse à laquelle tout s’est déroulé est simplement incroyable. De plus les développeurs sont intelligents et super sympa.
    6. +
    +

    Si vous êtes un “haskeller”, je pense que vous ne devriez pas avoir peur de la syntaxe particulière imposée par la façon standard de faire les choses avec yesod. Il faut essayer un peu plus loin que les premiers tutoriaux du livre.

    +

    Je pense que yesod va dans la bonne direction d’un web plus sûr et plus rapide. Même si je pense que l’avenir sera que les serveurs devront être limités à faire serveur d’API (JSON ou XML ou n’importe quel autre mode de représentation d’objets).

    +

    Yesod est juste incroyable. Dépassez les difficultés liées à l’apprentissage d’haskell et essayez le !

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-10-04 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/Yesod-tutorial-for-newbies/index.html b/Scratch/fr/blog/Yesod-tutorial-for-newbies/index.html new file mode 100644 index 0000000..6504372 --- /dev/null +++ b/Scratch/fr/blog/Yesod-tutorial-for-newbies/index.html @@ -0,0 +1,526 @@ + + + + + + YBlog - Site en Haskell + + + + + + + + + + + + + +
    + + +
    +

    Site en Haskell

    +
    +
    +
    +
    +

    Neo Flying at warp speed

    +
    + +

    mise à jour: mise à jour pour la version 0.10 de yesod.

    +

    tlpl: Un tutoriel pour yesod, un framework web Haskell. Vous ne devriez pas avoir besoin de savoir programmer en Haskell. Par contre je suis désolé pour les francophones, mais je n’ai pas eu le courage de traduire cet article en Français.

    +
    +
    +Table of content +
    + +
      +
    • Table of Content (generated) {:toc}
    • +
    +
    +
    + +

    Why Haskell?

    +

    Impressive Haskell Benchmark

    +

    Its efficiency (see [Snap Benchmark][snapbench] & Warp Benchmark[^benchmarkdigression]). Haskell is an order of magnitude faster than interpreted languages like [Ruby][haskellvsruby] and [Python][haskellvspython][^speeddigression].

    +

    Haskell is a high level language and make it harder to shoot you in the foot than C, C++ or Java for example. One of the best property of Haskell being:

    +
    +

    “If your program compile it will be very close to what the programmer intended”.

    +
    +

    Haskell web frameworks handle parallel tasks perfectly. For example even better than node.js[^nodejstroll].

    +

    Thousands of Agent Smith

    +

    From the pure technical point of view, Haskell seems to be the perfect web development tool. Weaknesses of Haskell certainly won’t be technical:

    +
      +
    • Hard to grasp Haskell
    • +
    • Hard to find a Haskell programmer
    • +
    • The Haskell community is smaller than the community for /.*/
    • +
    • There is no heroku for Haskell (even if Greg Weber did it, it was more a workaround).
    • +
    +

    I won’t say these are not important drawbacks. But, with Haskell your web application will have both properties to absorb an impressive number of parallel request securely and to adapt to change.

    +

    Actually there are three main Haskell web frameworks:

    +
      +
    1. Happstack
    2. +
    3. Snap
    4. +
    5. Yesod
    6. +
    +

    I don’t think there is a real winner between these three framework. The choice I made for yesod is highly subjective. I just lurked a bit and tried some tutorials. I had the feeling yesod make a better job at helping newcomers. Furthermore, apparently the yesod team seems the most active. Of course I might be wrong since it is a matter of feeling.

    +

    1. Draw some circles. 2. Draw the rest of the fucking owl

    +

    Why did I write this article? The yesod documentation and particularly the book are excellent. But I missed an intermediate tutorial. This tutorial won’t explain all details. I tried to give a step by step of how to start from a five minute tutorial to an almost production ready architecture. Furthermore explaining something to others is a great way to learn. If you are used to Haskell and Yesod, this tutorial won’t learn you much. If you are completely new to Haskell and Yesod it might hopefully helps you. Also if you find yourself too confused by the syntax, it might helps to read this article

    +

    During this tutorial you’ll install, initialize and configure your first yesod project. Then there is a very minimal 5 minutes yesod tutorial to heat up and verify the awesomeness of yesod. Then we will clean up the 5 minutes tutorial to use some “best practices”. Finally there will be a more standard real world example; a minimal blog system.

    +

    [snapbench]: http://snapframework.com/blog/2010/11/17/snap-0.3-benchmarks [^benchmarkdigression]: One can argue these benchmark contains many problems. But the benchmarks are just here to give an order of idea. Mainly Haskell is very fast. [^speeddigression]: Generally high level Haskell is slower than C, but low level Haskell is equivalent to C speed. It means that even if you can easily link C code with Haskell, this is not needed to reach the same speed. Furthermore writing a web service in C/C++ seems to be a very bad idea. You can take a look at a discussion on HN about this. [^nodejstroll]: If you are curious, you can search about the Fibonacci node.js troll. Without any tweaking, Haskell handled this problem perfectly. I tested it myself using yesod instead of Snap. [haskellvsruby]: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=ghc&lang2=yarv [haskellvspython]: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=ghc&lang2=python3

    +

    Before the real start

    +

    Install

    +

    The recommended way to install Haskell is to download the Haskell Platform.

    +

    Once done, you need to install yesod. Open a terminal session and do:

    +
    ~ cabal update
    +~ cabal install yesod cabal-dev
    +

    There are few steps but it should take some time to finish.

    +

    Initialize

    +

    You are now ready to initialize your first yesod project. Open a terminal and type:

    +
    ~ yesod init
    +

    Enter your name, choose yosog for the project name and enter Yosog for the name of the Foundation. Finally choose sqlite. Now, start the development cycle:

    +
    ~ cd yosog
    +~ cabal-dev install && yesod --dev devel
    +

    This will compile the entire project. Be patient it could take a while the first time. Once finished a server is launched and you could visit it by clicking this link:

    +

    http://localhost:3000

    +

    Congratulation! Yesod works!

    +
    + +

    Note: if something is messed up use the following command line inside the project directory.

    +
    \rm -rf dist/* ; cabal-dev install && yesod --dev devel
    +
    + +

    Until the end of the tutorial, use another terminal and let this one open in a corner to see what occurs.

    +

    Configure git

    +
    +

    Of course this step is not mandatory for the tutorial but it is a good practice.

    +
    +

    Copy this .gitignore file into the yosog folder.

    +
    cabal-dev
    +dist
    +.static-cache
    +static/tmp
    +*.sqlite3
    +

    Then initialize your git repository:

    +
    ~ git init .
    +~ git add .
    +~ git commit -a -m "Initial yesod commit"
    +

    We are almost ready to start.

    +

    Some last minute words

    +

    Up until here, we have a directory containing a bunch of files and a local web server listening the port 3000. If we modify a file inside this directory, yesod should try to recompile as fast as possible the site. Instead of explaining the role of every file, let’s focus only on the important files/directories for this tutorial:

    +
      +
    1. config/routes
    2. +
    3. Handler/
    4. +
    5. templates/
    6. +
    7. config/models
    8. +
    +

    Obviously:

    +

    config/routes | is where you’ll configure the map %url → Code. |
    Handler/ | contains the files that will contain the code called when a %url is accessed. |
    templates/ | contains html, js and css templates. |
    config/models | is where you’ll configure the persistent objects (database tables). |

    +

    During this tutorial we’ll modify other files as well, but we won’t explore them in detail.

    +

    Also note, shell commands are executed in the root directory of your project instead specified otherwise.

    +

    We are now ready to start!

    +

    Echo

    +

    To verify the quality of the security of the yesod framework, let’s make a minimal echo application.

    +
    +

    Goal:

    +

    Make a server that when accessed /echo/[some text] should return a web page containing “some text” inside an h1 bloc.

    +
    +

    In a first time, we must declare the %url of the form /echo/... are meaningful. Let’s take a look at the file config/routes:

    +
    +/static StaticR Static getStatic
    +/auth   AuthR   Auth   getAuth
    +
    +/favicon.ico FaviconR GET
    +/robots.txt RobotsR GET
    +
    +/ HomeR GET
    +
    + +

    We want to add a route of the form /echo/[anything] somehow and do some action with this. Add the following:

    +
    +/echo/#String EchoR GET
    +
    + +

    This line contains three elements: the %url pattern, a handler name, an %http method. I am not particularly fan of the big R notation but this is the standard convention.

    +

    If you save config/routes, you should see your terminal in which you launched yesod devel activate and certainly displaying an error message.

    +
    +Application.hs:31:1: Not in scope: `getEchoR'
    +
    + +

    Why? Simply because we didn’t written the code for the handler EchoR. Edit the file Handler/Home.hs and append this:

    +
    getEchoR :: String -> Handler RepHtml
    +getEchoR theText = do
    +    defaultLayout $ do
    +        [whamlet|<h1>#{theText}|]
    +

    Don’t worry if you find all of this a bit cryptic. In short it just declare a function named getEchoR with one argument (theText) of type String. When this function is called, it return a Handler RepHtml whatever it is. But mainly this will encapsulate our expected result inside an html text.

    +

    After saving the file, you should see yesod recompile the application. When the compilation is finished you’ll see the message: Starting devel application.

    +

    Now you can visit: http://localhost:3000/echo/Yesod%20rocks!

    +

    TADA! It works!

    +

    Bulletproof?

    +

    Neo stops a myriad of bullets

    +

    Even this extremely minimal web application has some impressive properties. For exemple, imagine an attacker entering this %url:

    +[http://localhost:3000/echo/<a>I'm <script>alert("Bad!");](http://localhost:3000/echo/I’m + +

    " %>

    +

    The special characters are protected for us. A malicious user could not hide some bad script inside.

    +

    This behavior is a direct consequence of type safety. The %url string is put inside a %url type. Then the interesting part in the %url is put inside a String type. To pass from %url type to String type some transformation are made. For example, replace all “%20” by space characters. Then to show the String inside an html document, the string is put inside an html type. Some transformations occurs like replace “<” by “&lt;”. Thanks to yesod, this tedious job is done for us.

    +
    "http://localhost:3000/echo/some%20text<a>" :: URL
    +                    ↓
    +              "some text<a>"                 :: String
    +                    ↓
    +          "some text &amp;lt;a&amp;gt;"             :: Html 
    +

    Yesod is not only fast, it helps us to remain secure. It protects us from many common errors in other paradigms. Yes, I am looking at you PHP!

    +

    Cleaning up

    +

    Even this very minimal example should be enhanced. We will clean up many details:

    +
    +

    Use a better css

    +

    It is nice to note, the default template is based on %html5 boilerplate. Let’s change the default css. Add a file named default-layout.lucius inside the templates/ directory containing:

    +
    body {
    +    font-family: Helvetica, sans-serif; 
    +    font-size: 18px; }
    +#main {
    +    padding: 1em;
    +    border: #CCC solid 2px;
    +    border-radius: 5px;
    +    margin: 1em;
    +    width: 37em;
    +    margin: 1em auto;
    +    background: #F2F2F2;
    +    line-height: 1.5em;
    +    color: #333; }
    +.required { margin: 1em 0; }
    +.optional { margin: 1em 0; }
    +label { width: 8em; display: inline-block; }
    +input, textarea { background: #FAFAFA}
    +textarea { width: 27em; height: 9em;}
    +ul { list-style: square; }
    +a { color: #A56; }
    +a:hover { color: #C58; }
    +a:active { color: #C58; }
    +a:visited { color: #943; }
    +

    Personally I would prefer if such a minimal css was put with the scaffolding tool. I am sure somebody already made such a minimal css which give the impression the browser handle correctly html without any style applied to it. But I digress.

    +

    Separate Handlers

    +

    Generally you don’t want to have all your code inside a unique file. This is why we will separate our handlers. In a first time create a new file Handler/Echo.hs containing:

    +
    module Handler.Echo where
    +
    +import Import
    +
    +getEchoR :: String -> Handler RepHtml
    +getEchoR theText = do
    +    defaultLayout $ do
    +        [whamlet|<h1>#{theText}|]
    +

    Do not forget to remove the getEchoR function inside Handler/Home.hs.

    +

    We must declare this new file intoyosog.cabal. Just after Handler.Home, add:

    +
    +    Handler.Echo
    +
    + +

    We must also declare this new Handler module inside Application.hs. Just after the “import Handler.Home”, add:

    +
    import Handler.Echo
    +

    This is it.

    +

    ps: I am sure not so far in the future we could simply write yesod add-handler Echo to declare it and create a new handler file.

    +

    Data.Text

    +

    It is a good practice to use Data.Text instead of String.

    +

    To declare it, add this import directive to Foundation.hs (just after the last one):

    +
    import Data.Text
    +

    We have to modify config/routes and our handler accordingly. Replace #String by #Text in config/routes:

    +
    +/echo/#Text EchoR GET
    +
    + +

    And do the same in Handler/Echo.hs:

    +
    module Handler.Echo where
    +
    +import Import
    +
    +getEchoR :: Text -> Handler RepHtml
    +getEchoR theText = do
    +    defaultLayout $ do
    +        [whamlet|<h1>#{theText}|]
    +

    Use templates

    +

    Some html (more precisely hamlet) is written directly inside our handler. We should put this part inside another file. Create the new file templates/echo.hamlet containing:

    +
    <h1> #{theText}
    +

    and modify the handler Handler/Echo.hs:

    +
    getEchoR :: Text -> Handler RepHtml
    +getEchoR theText = do
    +    defaultLayout $ do
    +        $(widgetFile "echo")
    +

    At this point, our web application is structured between different files. Handler are grouped, we use Data.Text and our views are in templates. It is the time to make a slightly more complex example.

    +

    Mirror

    +

    Neo touching a mirror

    +

    Let’s make another minimal application. You should see a form containing a text field and a validation button. When you enter some text (for example “Jormungad”) and validate, the next page present you the content and its reverse appended to it. In our example it should return “JormungaddagnumroJ”.

    +

    First, add a new route:

    +
    +/mirror MirrorR GET POST
    +
    + +

    This time the path /mirror will accept GET and POST requests. Add the corresponding new Handler file:

    +
    module Handler.Mirror where
    +
    +import Import
    +import qualified Data.Text as T
    +
    +getMirrorR :: Handler RepHtml
    +getMirrorR = do
    +    defaultLayout $ do
    +        $(widgetFile "mirror")
    +
    +postMirrorR :: Handler RepHtml
    +postMirrorR =  do
    +        postedText <- runInputPost $ ireq textField "content"
    +        defaultLayout $ do
    +            $(widgetFile "posted")
    +

    Don’t forget to declare it inside yosog.cabal and Application.hs.

    +

    We will need to use the reverse function provided by Data.Text which explain the additional import.

    +

    The only new thing here is the line that get the POST parameter named “content”. If you want to know more detail about it and form in general you can take look at the yesod book.

    +

    Create the two corresponding templates:

    +
    <h1> Enter your text
    +<form method=post action=@{MirrorR}>
    +    <input type=text name=content>
    +    <input type=submit>
    +
    <h1>You've just posted
    +<p>#{postedText}#{T.reverse postedText}
    +<hr>
    +<p><a href=@{MirrorR}>Get back
    +

    And that is all. This time, we won’t need to clean up. We may have used another way to generate the form but we’ll see this in the next section.

    +

    Just try it by clicking here.

    +

    Also you can try to enter strange values. Like before, your application is quite secure.

    +

    A Blog

    +

    We saw how to retrieve %http parameters. It is the time to save things into a database.

    +

    As before add some routes inside config/routes:

    +
    +/blog               BlogR       GET POST
    +/blog/#ArticleId    ArticleR    GET
    +
    + +

    This example will be very minimal:

    +
      +
    • GET on /blog should display the list of articles.
    • +
    • POST on /blog should create a new article
    • +
    • GET on /blog/<article id> should display the content of the article.
    • +
    +

    First we declare another model object. Append the following content to config/models:

    +
    +Article
    +    title   Text
    +    content Html 
    +    deriving
    +
    + +

    As Html is not an instance of Read, Show and Eq, we had to add the deriving line. If you forget it, there will be an error.

    +

    After the route and the model, we write the handler. First, declare a new Handler module. Add import Handler.Blog inside Application.hs and add it into yosog.cabal. Let’s write the content of Handler/Blog.hs. We start by declaring the module and by importing some block necessary to handle Html in forms.

    +
    module Handler.Blog
    +    ( getBlogR
    +    , postBlogR
    +    , getArticleR
    +    )
    +where
    +
    +import Import
    +import Data.Monoid
    +
    +-- to use Html into forms
    +import Yesod.Form.Nic (YesodNic, nicHtmlField)
    +instance YesodNic App
    +

    Remark: it is a best practice to add the YesodNic instance inside Foundation.hs. I put this definition here to make things easier but you should see a warning about this orphan instance. To put the include inside Foundation.hs is left as an exercice to the reader.

    +

    Hint: Do not forget to put YesodNic and nicHtmlField inside the exported objects of the module.

    +
    entryForm :: Form Article
    +entryForm = renderDivs $ Article
    +    <$> areq   textField "Title" Nothing
    +    <*> areq   nicHtmlField "Content" Nothing
    +

    This function defines a form for adding a new article. Don’t pay attention to all the syntax. If you are curious you can take a look at Applicative Functor. You just have to remember areq is for required form input. Its arguments being: areq type label default_value.

    +
    -- The view showing the list of articles
    +getBlogR :: Handler RepHtml
    +getBlogR = do
    +    -- Get the list of articles inside the database.
    +    articles <- runDB $ selectList [] [Desc ArticleTitle]
    +    -- We'll need the two "objects": articleWidget and enctype
    +    -- to construct the form (see templates/articles.hamlet).
    +    (articleWidget, enctype) <- generateFormPost entryForm
    +    defaultLayout $ do
    +        $(widgetFile "articles")
    +

    This handler should display a list of articles. We get the list from the DB and we construct the form. Just take a look at the corresponding template:

    +
    <h1> Articles
    +$if null articles
    +    -- Show a standard message if there is no article
    +    <p> There are no articles in the blog
    +$else
    +    -- Show the list of articles
    +    <ul>
    +        $forall Entity articleId article <- articles
    +            <li> 
    +                <a href=@{ArticleR articleId} > #{articleTitle article}
    +<hr>
    +  <form method=post enctype=#{enctype}>
    +    ^{articleWidget}
    +    <div>
    +        <input type=submit value="Post New Article">
    +

    You should remark we added some logic inside the template. There is a test and a “loop”.

    +

    Another very interesting part is the creation of the form. The articleWidget was created by yesod. We have given him the right parameters (input required or optional, labels, default values). And now we have a protected form made for us. But we have to create the submit button.

    +

    Get back to Handler/Blog.hs.

    +
    -- we continue Handler/Blog.hs
    +postBlogR :: Handler RepHtml
    +postBlogR = do
    +    ((res,articleWidget),enctype) <- runFormPost entryForm
    +    case res of 
    +         FormSuccess article -> do 
    +            articleId <- runDB $ insert article
    +            setMessage $ toHtml $ (articleTitle article) <> " created"
    +            redirect $ ArticleR articleId 
    +         _ -> defaultLayout $ do
    +                setTitle "Please correct your entry form"
    +                $(widgetFile "articleAddError")
    +

    This function should be used to create a new article. We handle the form response. If there is an error we display an error page. For example if we left some required value blank. If things goes right:

    +
      +
    • we add the new article inside the DB (runDB $ insert article)
    • +
    • we add a message to be displayed (setMessage $ ...)
    • +
    • we are redirected to the article web page.
    • +
    +

    Here is the content of the error Page:

    +
    <form method=post enctype=#{enctype}>
    +    ^{articleWidget}
    +    <div>
    +        <input type=submit value="Post New Article">
    +

    Finally we need to display an article:

    +
    getArticleR :: ArticleId -> Handler RepHtml
    +getArticleR articleId = do
    +    article <- runDB $ get404 articleId
    +    defaultLayout $ do
    +        setTitle $ toHtml $ articleTitle article
    +        $(widgetFile "article")
    +

    The get404 function try to do a get on the DB. If it fails it return a 404 page. The rest should be clear. Here is the content of templates/article.hamlet:

    +
    <h1> #{articleTitle article}
    +<article> #{articleContent article}
    +

    The blog system is finished. Just for fun, you can try to create an article with the following content:

    +
    <p>A last try to <em>cross script</em> 
    +   and <em>SQL injection</em></p>
    +<p>Here is the first try: 
    +   <script>alert("You loose");</script></p>
    +<p> And Here is the last </p>
    +"); DROP TABLE ARTICLE;;
    +

    Conclusion

    +

    This is the end of this tutorial. I made it very minimal.

    +

    If you already know Haskell and you want to go further, you should take a look at the recent i18n blog tutorial. It will be obvious I inspired my own tutorial on it. You’ll learn in a very straightforward way how easy it is to use authorizations, Time and internationalization.

    +

    If, on the other hand you don’t know Haskell. Then you shouldn’t jump directly to web programming. Haskell is a very complex and unusual language. My advice to go as fast as possible in using Haskell for web programming is:

    +
      +
    1. Start by try Haskell in your browser
    2. +
    3. Then read the excellent Learn you a Haskell for Great Good
    4. +
    5. If you have difficulties in understanding concepts like monads, you should really read these articles. For me they were enlightening.
    6. +
    7. If you feel confident, you should be able to follows the yesod book and if you find difficult to follows the yesod book, you should read real world Haskell first (it is a must read).
    8. +
    +

    Also, note that:

    +
      +
    • haskell.org is full of excellent resources.
    • +
    • hoogle will be very useful
    • +
    • Use hlint as soon as possible to get good habits.
    • +
    +

    As you should see, if you don’t already know Haskell, the path is long but I guaranty you it will be very rewarding!

    +

    ps: You can download the source of this yesod blog tutorial at github.com/yogsototh/yosog.

    +
    +
    +
      +
    1. By view I mean yesod widget’s hamlet, lucius and julius files.

    2. +
    +
    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2012-01-15 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/feed/feed.xml b/Scratch/fr/blog/feed/feed.xml new file mode 100644 index 0000000..cfe47b7 --- /dev/null +++ b/Scratch/fr/blog/feed/feed.xml @@ -0,0 +1,5669 @@ + + + yannesposito - Yann Esposito + + + http://yannesposito.com/Scratch/fr/blog/feed/feed.xml + + Yann Esposito + yann.esposito@gmail.com + + 2012-12-12T00:00:00Z + + Category Theory Presentation + + http://yannesposito.com/Scratch/fr/blog/Category-Theory-Presentation/index.html + 2012-12-12T00:00:00Z + 2012-12-12T00:00:00Z +
    + +

    Yesterday I was happy to make a presentation about Category Theory at Riviera Scala Clojure Meetup (note I used only Haskell for my examples).

    + + + +

    If you don't want to read them through an HTML presentations framework or downloading a big PDF +just continue to read as a standard web page. +

    + +
    +\(\newcommand{\F}{\mathbf{F}}\) +\(\newcommand{\E}{\mathbf{E}}\) +\(\newcommand{\C}{\mathcal{C}}\) +\(\newcommand{\D}{\mathcal{D}}\) +\(\newcommand{\id}{\mathrm{id}}\) +\(\newcommand{\ob}[1]{\mathrm{ob}(#1)}\) +\(\newcommand{\hom}[1]{\mathrm{hom}(#1)}\) +\(\newcommand{\Set}{\mathbf{Set}}\) +\(\newcommand{\Mon}{\mathbf{Mon}}\) +\(\newcommand{\Vec}{\mathbf{Vec}}\) +\(\newcommand{\Grp}{\mathbf{Grp}}\) +\(\newcommand{\Rng}{\mathbf{Rng}}\) +\(\newcommand{\ML}{\mathbf{ML}}\) +\(\newcommand{\Hask}{\mathbf{Hask}}\) +\(\newcommand{\Cat}{\mathbf{Cat}}\) +\(\newcommand{\fmap}{\mathtt{fmap}}\) +
    + +
    +

    Category Theory & Programming

    +
    for Rivieria Scala Clojure (Note this presentation uses Haskell)
    +by Yann Esposito +
    + + @yogsototh, + + + +yogsototh + +
    +
    +
    +

    Plan

    +
      +
    • General overview
    • +
    • Definitions
    • +
    • Applications
    • +
    +
    +
    +

    Not really about: Cat & glory

    +
    +Cat n glory
    credit to Tokuhiro Kawai (川井徳寛)
    +
    + +
    +
    +

    General Overview

    +
    +Samuel Eilenberg Saunders Mac Lane +
    + +

    Recent Math Field
    1942-45, Samuel Eilenberg & Saunders Mac Lane

    +

    Certainly one of the more abstract branches of math

    +
      +
    • New math foundation
      formalism abstraction, package entire theory
    • +
    • Bridge between disciplines
      Physics, Quantum Physics, Topology, Logic, Computer Science
    • +
    +

    +★: When is one thing equal to some other thing?, Barry Mazur, 2007
    ☆: Physics, Topology, Logic and Computation: A Rosetta Stone, John C. Baez, Mike Stay, 2009 +

    + +
    +
    +

    From a Programmer perspective

    +
    +

    Category Theory is a new language/framework for Math

    +
    +
      +
    • Another way of thinking
    • +
    • Extremely efficient for generalization
    • +
    +
    +
    +

    Math Programming relation

    +Buddha Fractal +

    Programming is doing Math

    +

    Strong relations between type theory and category theory.

    +

    Not convinced?
    Certainly a vocabulary problem.

    +

    One of the goal of Category Theory is to create a homogeneous vocabulary between different disciplines.

    +
    +
    +

    Vocabulary

    +mind blown +

    Math vocabulary used in this presentation:

    +
    +

    Category, Morphism, Associativity, Preorder, Functor, Endofunctor, Categorial property, Commutative diagram, Isomorph, Initial, Dual, Monoid, Natural transformation, Monad, Klesli arrows, κατα-morphism, ...

    +
    +
    +
    +

    Programmer Translation

    +lolcat + + + + + + + + +
    +Mathematician + +Programmer +
    +Morphism + +Arrow +
    +Monoid + +String-like +
    +Preorder + +Acyclic graph +
    +Isomorph + +The same +
    +Natural transformation + +rearrangement function +
    +Funny Category + +LOLCat +
    + +
    +
    +

    Plan

    +
      +
    • General overview
    • +
    • Definitions +
        +
      • Category
      • +
      • Intuition
      • +
      • Examples
      • +
      • Functor
      • +
      • Examples
      • +
      +
    • +
    • Applications
    • +
    +
    +
    +

    Category

    + +

    A way of representing things and ways to go between things.

    + +

    A Category \(\mathcal{C}\) is defined by:

    +
      +
    • Objects \(\ob{C}\),
    • +
    • Morphisms \(\hom{C}\),
    • +
    • a Composition law (∘)
    • +
    • obeying some Properties.
    • +
    +
    +
    +

    Category: Objects

    + +objects + +

    \(\ob{\mathcal{C}}\) is a collection

    +
    +
    +

    Category: Morphisms

    + +morphisms + +

    \(A\) and \(B\) objects of \(\C\)
    +\(\hom{A,B}\) is a collection of morphisms
    +\(f:A→B\) denote the fact \(f\) belongs to \(\hom{A,B}\)

    +

    \(\hom{\C}\) the collection of all morphisms of \(\C\)

    +
    +
    +

    Category: Composition

    +

    Composition (∘): associate to each couple \(f:A→B, g:B→C\) + $$g∘f:A\rightarrow C$$ +

    +composition +
    +
    +

    Category laws: neutral element

    +

    for each object \(X\), there is an \(\id_X:X→X\),
    +such that for each \(f:A→B\):

    +identity +
    +
    +

    Category laws: Associativity

    +

    Composition is associative:

    +associative composition +
    +
    +

    Commutative diagrams

    + +

    Two path with the same source and destination are equal.

    +
    + Commutative Diagram (Associativity) +
    + \((h∘g)∘f = h∘(g∘f) \) +
    +
    +
    + Commutative Diagram (Identity law) +
    + \(id_B∘f = f = f∘id_A \) +
    +
    +
    +
    +

    Question Time!

    + +
    + +
    +- French-only joke - +
    +
    +
    +
    +

    Can this be a category?

    +

    \(\ob{\C},\hom{\C}\) fixed, is there a valid ∘?

    +
    + Category example 1 +
    + YES +
    +
    +
    + Category example 2 +
    + no candidate for \(g∘f\) +
    NO +
    +
    +
    + Category example 3 +
    + YES +
    +
    +
    +
    +

    Can this be a category?

    +
    + Category example 4 +
    + no candidate for \(f:C→B\) +
    NO +
    +
    +
    + Category example 5 +
    + \((h∘g)∘f=\id_B∘f=f\)
    + \(h∘(g∘f)=h∘\id_A=h\)
    + but \(h≠f\)
    + NO +
    +
    +
    +
    +

    Categories Examples

    + +
    +Basket of cats +
    +- Basket of Cats - +
    +
    +
    +
    +

    Category \(\Set\)

    + +
      +
    • \(\ob{\Set}\) are all the sets
    • +
    • \(\hom{E,F}\) are all functions from \(E\) to \(F\)
    • +
    • ∘ is functions composition
    • +
    + +
      +
    • \(\ob{\Set}\) is a proper class ; not a set
    • +
    • \(\hom{E,F}\) is a set
    • +
    • \(\Set\) is then a locally small category
    • +
    +
    +
    +

    Categories Everywhere?

    +Cats everywhere +
      +
    • \(\Mon\): (monoids, monoid morphisms,∘)
    • +
    • \(\Vec\): (Vectorial spaces, linear functions,∘)
    • +
    • \(\Grp\): (groups, group morphisms,∘)
    • +
    • \(\Rng\): (rings, ring morphisms,∘)
    • +
    • Any deductive system T: (theorems, proofs, proof concatenation)
    • +
    • \( \Hask\): (Haskell types, functions, (.) )
    • +
    • ...
    • +
    +
    +
    +

    Smaller Examples

    + +

    Strings

    +Monoids are one object categories +
      +
    • \(\ob{Str}\) is a singleton
    • +
    • \(\hom{Str}\) each string
    • +
    • ∘ is concatenation (++)
    • +
    +
      +
    • "" ++ u = u = u ++ ""
    • +
    • (u ++ v) ++ w = u ++ (v ++ w)
    • +
    +
    +
    +

    Finite Example?

    + +

    Graph

    +
    +Each graph is a category +
    +
      +
    • \(\ob{G}\) are vertices
    • +
    • \(\hom{G}\) each path
    • +
    • ∘ is path concatenation
    • +
    +
    • \(\ob{G}=\{X,Y,Z\}\), +
    • \(\hom{G}=\{ε,α,β,γ,αβ,βγ,...\}\) +
    • \(αβ∘γ=αβγ\) +
    +
    +
    +

    Number construction

    + +

    Each Numbers as a whole category

    +Each number as a category +
    +
    +

    Degenerated Categories: Monoids

    + +Monoids are one object categories +

    Each Monoid \((M,e,⊙): \ob{M}=\{∙\},\hom{M}=M,\circ = ⊙\)

    +

    Only one object.

    +

    Examples:

    +
    • (Integer,0,+), (Integer,1,*), +
    • (Strings,"",++), for each a, ([a],[],++) +
    +
    +
    +

    Degenerated Categories: Preorders \((P,≤)\)

    + +
    • \(\ob{P}={P}\), +
    • \(\hom{x,y}=\{x≤y\} ⇔ x≤y\), +
    • \((y≤z) \circ (x≤y) = (x≤z) \) +
    + +

    At most one morphism between two objects.

    + +preorder category +
    +
    +

    Degenerated Categories: Discrete Categories

    + +Any set can be a category +

    Any Set

    +

    Any set \(E: \ob{E}=E, \hom{x,y}=\{x\} ⇔ x=y \)

    +

    Only identities

    +
    +
    +

    Choice

    +

    The same object can be seen in many different way as a category.

    +

    You can choose what are object, morphisms and composition.

    +

    ex: Str and discrete(Σ*)

    +
    +
    +

    Categorical Properties

    + +

    Any property which can be expressed in term of category, objects, morphism and composition.

    + +
    • Dual: \(\D\) is \(\C\) with reversed morphisms. +
    • Initial: \(Z\in\ob{\C}\) s.t. \(∀Y∈\ob{\C}, \#\hom{Z,Y}=1\) +
      Unique ("up to isormophism") +
    • Terminal: \(T\in\ob{\C}\) s.t. \(T\) is initial in the dual of \(\C\) +
    • Functor: structure preserving mapping between categories +
    • ... +
    +
    +
    +

    Isomorph

    +

    isomorph cats isomorphism: \(f:A→B\) which can be "undone" i.e.
    \(∃g:B→A\), \(g∘f=id_A\) & \(f∘g=id_B\)
    in this case, \(A\) & \(B\) are isomorphic.

    +

    A≌B means A and B are essentially the same.
    In Category Theory, = is in fact mostly .
    For example in commutative diagrams.

    +
    +
    +

    Functor

    + +

    A functor is a mapping between two categories. +Let \(\C\) and \(\D\) be two categories. +A functor \(\F\) from \(\C\) to \(\D\):

    +
      +
    • Associate objects: \(A\in\ob{\C}\) to \(\F(A)\in\ob{\D}\)
    • +
    • Associate morphisms: \(f:A\to B\) to \(\F(f) : \F(A) \to \F(B)\) + such that +
        +
      • \( \F (\)\(\id_X\)\()= \)\(\id\)\(\vphantom{\id}_{\F(}\)\(\vphantom{\id}_X\)\(\vphantom{\id}_{)} \),
      • +
      • \( \F (\)\(g∘f\)\()= \)\( \F(\)\(g\)\() \)\(\circ\)\( \F(\)\(f\)\() \)
      • +
      +
    • +
    +
    +
    +

    Functor Example (ob → ob)

    + +Functor +
    +
    +

    Functor Example (hom → hom)

    + +Functor +
    +
    +

    Functor Example

    + +Functor +
    +
    +

    Endofunctors

    + +

    An endofunctor for \(\C\) is a functor \(F:\C→\C\).

    +Endofunctor +
    +
    +

    Category of Categories

    + + + +

    Categories and functors form a category: \(\Cat\)

    +
    • \(\ob{\Cat}\) are categories +
    • \(\hom{\Cat}\) are functors +
    • ∘ is functor composition +
    +
    +
    +

    Plan

    +
      +
    • General overview
    • +
    • Definitions
    • +
    • Applications +
        +
      • \(\Hask\) category +
      • Functors +
      • Natural transformations +
      • Monads +
      • κατα-morphisms +
      +
    • +
    +
    +
    +

    Hask

    + +

    Category \(\Hask\):

    + +Haskell Category Representation + +
    • +\(\ob{\Hask} = \) Haskell types +
    • +\(\hom{\Hask} = \) Haskell functions +
    • +∘ = (.) Haskell function composition +
    + +

    Forget glitches because of undefined.

    +
    +
    +

    Haskell Kinds

    +

    In Haskell some types can take type variable(s). Typically: [a].

    +

    Types have kinds; The kind is to type what type is to function. Kind are the types for types (so meta).

    +
    Int, Char :: *
    +[], Maybe :: * -> *
    +(,), (->) :: * -> * -> *
    +[Int], Maybe Char, Maybe [Int] :: *
    +
    +
    +

    Haskell Types

    +

    Sometimes, the type determine a lot about the function:

    +
    fst :: (a,b) -> a -- Only one choice
    +snd :: (a,b) -> b -- Only one choice
    +f :: a -> [a]     -- Many choices
    +-- Possibilities: f x=[], or [x], or [x,x] or [x,...,x]
    +
    +? :: [a] -> [a] -- Many choices
    +-- can only rearrange: duplicate/remove/reorder elements
    +-- for example: the type of addOne isn't [a] -> [a]
    +addOne l = map (+1) l
    +-- The (+1) force 'a' to be a Num.
    + +

    +

    ★:Theorems for free!, Philip Wadler, 1989

    +
    +
    +

    Haskell Functor vs \(\Hask\) Functor

    + +

    A Haskell Functor is a type F :: * -> * which belong to the type class Functor ; thus instantiate +fmap :: (a -> b) -> (F a -> F b). + +

    & F: \(\ob{\Hask}→\ob{\Hask}\)
    & fmap: \(\hom{\Hask}→\hom{\Hask}\) + +

    The couple (F,fmap) is a \(\Hask\)'s functor if for any x :: F a:

    +
    • fmap id x = x +
    • fmap (f.g) x= (fmap f . fmap g) x +
    +
    +
    +

    Haskell Functors Example: Maybe

    + +
    data Maybe a = Just a | Nothing
    +instance Functor Maybe where
    +    fmap :: (a -> b) -> (Maybe a -> Maybe b)
    +    fmap f (Just a) = Just (f a)
    +    fmap f Nothing = Nothing
    +
    fmap (+1) (Just 1) == Just 2
    +fmap (+1) Nothing  == Nothing
    +fmap head (Just [1,2,3]) == Just 1
    +
    +
    +

    Haskell Functors Example: List

    + +
    instance Functor ([]) where
    +	fmap :: (a -> b) -> [a] -> [b]
    +	fmap = map
    +
    fmap (+1) [1,2,3]           == [2,3,4]
    +fmap (+1) []                == []
    +fmap head [[1,2,3],[4,5,6]] == [1,4]
    +
    +
    +

    Haskell Functors for the programmer

    +

    Functor is a type class used for types that can be mapped over.

    +
      +
    • Containers: [], Trees, Map, HashMap...
    • +
    • "Feature Type": +
        +
      • Maybe a: help to handle absence of a.
        Ex: safeDiv x 0 ⇒ Nothing
      • +
      • Either String a: help to handle errors
        Ex: reportDiv x 0 ⇒ Left "Division by 0!"
      • +
    • +
    +
    +
    +

    Haskell Functor intuition

    + +

    Put normal function inside a container. Ex: list, trees...

    + +Haskell Functor as a box play +

    +
    +

    Haskell Functor properties

    + +

    Haskell Functors are:

    + +
    • endofunctors ; \(F:\C→\C\) here \(\C = \Hask\), +
    • a couple (Object,Morphism) in \(\Hask\). +
    +
    +
    +

    Functor as boxes

    + +

    Haskell functor can be seen as boxes containing all Haskell types and functions. +Haskell types look like a fractal:

    + +Haskell functor representation +
    +
    +

    Functor as boxes

    + +

    Haskell functor can be seen as boxes containing all Haskell types and functions. +Haskell types look like a fractal:

    + +Haskell functor representation +
    +
    +

    Functor as boxes

    + +

    Haskell functor can be seen as boxes containing all Haskell types and functions. +Haskell types look like a fractal:

    + +Haskell functor representation +
    +
    +

    "Non Haskell" Hask's Functors

    +

    A simple basic example is the \(id_\Hask\) functor. It simply cannot be expressed as a couple (F,fmap) where

    +
      +
    • F::* -> *
    • +
    • fmap :: (a -> b) -> (F a) -> (F b)
    • +
    +

    Another example:

    +
      +
    • F(T)=Int
    • +
    • F(f)=\_->0
    • +
    +
    +
    +

    Also Functor inside \(\Hask\)

    +

    \(\mathtt{[a]}∈\ob{\Hask}\) but is also a category. Idem for Int.

    +

    length is a Functor from the category [a] to the category Int:

    +
      +
    • \(\ob{\mathtt{[a]}}=\{∙\}\)
    • +
    • \(\hom{\mathtt{[a]}}=\mathtt{[a]}\)
    • +
    • \(∘=\mathtt{(++)}\)
    • +
    +

    +
      +
    • \(\ob{\mathtt{Int}}=\{∙\}\)
    • +
    • \(\hom{\mathtt{Int}}=\mathtt{Int}\)
    • +
    • \(∘=\mathtt{(+)}\)
    • +
    +
    +
    • id: length [] = 0 +
    • comp: length (l ++ l') = (length l) + (length l') +
    +
    +
    +

    Category of \(\Hask\) Endofunctors

    +Category of Hask endofunctors +
    +
    +

    Category of Functors

    +

    If \(\C\) is small (\(\hom{\C}\) is a set). All functors from \(\C\) to some category \(\D\) form the category \(\mathrm{Func}(\C,\D)\).

    +
      +
    • \(\ob{\mathrm{Func}(\C,\D)}\): Functors \(F:\C→\D\)
    • +
    • \(\hom{\mathrm{Func}(\C,\D)}\): natural transformations
    • +
    • ∘: Functor composition
    • +
    +

    \(\mathrm{Func}(\C,\C)\) is the category of endofunctors of \(\C\).

    +
    +
    +

    Natural Transformations

    +

    Let \(F\) and \(G\) be two functors from \(\C\) to \(\D\).

    +

    Natural transformation commutative diagram A natural transformation: familly η ; \(η_X\in\hom{\D}\) for \(X\in\ob{\C}\) s.t.

    +

    ex: between Haskell functors; F a -> G a
    Rearragement functions only.

    +
    +
    +

    Natural Transformation Examples (1/4)

    +
    data List a = Nil | Cons a (List a)
    +toList :: [a] -> List a
    +toList [] = Nil
    +toList (x:xs) = Cons x (toList xs)
    +

    toList is a natural transformation. It is also a morphism from [] to List in the Category of \(\Hask\) endofunctors.

    +natural transformation commutative diagram +
    +natural transformation commutative diagram +
    + +
    +
    +

    Natural Transformation Examples (2/4)

    +
    data List a = Nil | Cons a (List a)
    +toHList :: List a -> [a]
    +toHList Nil = []
    +toHList (Cons x xs) = x:toHList xs
    +

    toHList is a natural transformation. It is also a morphism from List to [] in the Category of \(\Hask\) endofunctors.

    +natural transformation commutative diagram +
    +natural transformation commutative diagram
    toList . toHList = id & toHList . toList = id &
    therefore [] & List are isomorph.
    +
    + +
    +
    +

    Natural Transformation Examples (3/4)

    +
    toMaybe :: [a] -> Maybe a
    +toMaybe [] = Nothing
    +toMaybe (x:xs) = Just x
    +

    toMaybe is a natural transformation. It is also a morphism from [] to Maybe in the Category of \(\Hask\) endofunctors.

    +natural transformation commutative diagram +
    +natural transformation commutative diagram +
    + +
    +
    +

    Natural Transformation Examples (4/4)

    +
    mToList :: Maybe a -> [a]
    +mToList Nothing = []
    +mToList Just x  = [x]
    +

    toMaybe is a natural transformation. It is also a morphism from [] to Maybe in the Category of \(\Hask\) endofunctors.

    +natural transformation commutative diagram +
    +relation between [] and Maybe
    There is no isomorphism.
    Hint: Bool lists longer than 1.
    +
    + +
    +
    +

    Composition problem

    +

    The Problem; example with lists:

    +
    f x = [x]       ⇒ f 1 = [1]   ⇒ (f.f) 1 = [[1]] ✗
    +g x = [x+1]     ⇒ g 1 = [2]   ⇒ (g.g) 1 = ERROR [2]+1 ✗
    +h x = [x+1,x*3] ⇒ h 1 = [2,3] ⇒ (h.h) 1 = ERROR [2,3]+1 ✗ 
    + +

    The same problem with most f :: a -> F a functions and functor F.

    +
    +
    +

    Composition Fixable?

    +

    How to fix that? We want to construct an operator which is able to compose:

    +

    f :: a -> F b & g :: b -> F c.

    +

    More specifically we want to create an operator ◎ of type

    +

    ◎ :: (b -> F c) -> (a -> F b) -> (a -> F c)

    +

    Note: if F = I, ◎ = (.).

    +
    +
    +

    Fix Composition (1/2)

    +

    Goal, find: ◎ :: (b -> F c) -> (a -> F b) -> (a -> F c)
    f :: a -> F b, g :: b -> F c:

    +
      +
    • (g ◎ f) x ???
    • +
    • First apply f to xf x :: F b
    • +
    • Then how to apply g properly to an element of type F b?
    • +
    +
    +
    +

    Fix Composition (2/2)

    +

    Goal, find: ◎ :: (b -> F c) -> (a -> F b) -> (a -> F c)
    f :: a -> F b, g :: b -> F c, f x :: F b:

    +
      +
    • Use fmap :: (t -> u) -> (F t -> F u)!
    • +
    • (fmap g) :: F b -> F (F c) ; (t=b, u=F c)
    • +
    • (fmap g) (f x) :: F (F c) it almost WORKS!
    • +
    • We lack an important component, join :: F (F c) -> F c
    • +
    • (g ◎ f) x = join ((fmap g) (f x))
      ◎ is the Kleisli composition; in Haskell: <=< (in Control.Monad).
    • +
    +
    +
    +

    Necessary laws

    +

    For ◎ to work like composition, we need join to hold the following properties:

    +
      +
    • join (join (F (F (F a))))=join (F (join (F (F a))))
    • +
    • abusing notations denoting join by ⊙; this is equivalent to
      (F ⊙ F) ⊙ F = F ⊙ (F ⊙ F)
    • +
    • There exists η :: a -> F a s.t.
      η⊙F=F=F⊙η
    • +
    +
    +
    +

    Klesli composition

    +

    Now the composition works as expected. In Haskell ◎ is <=< in Control.Monad.

    +

    g <=< f = \x -> join ((fmap g) (f x))

    +
    f x = [x]       ⇒ f 1 = [1]   ⇒ (f <=< f) 1 = [1] ✓
    +g x = [x+1]     ⇒ g 1 = [2]   ⇒ (g <=< g) 1 = [3] ✓
    +h x = [x+1,x*3] ⇒ h 1 = [2,3] ⇒ (h <=< h) 1 = [3,6,4,9] ✓
    + +
    +
    +

    We reinvented Monads!

    +

    A monad is a triplet (M,⊙,η) where

    +
      +
    • \(M\) an Endofunctor (to type a associate M a)
    • +
    • \(⊙:M×M→M\) a nat. trans. (i.e. ⊙::M (M a) → M a ; join)
    • +
    • \(η:I→M\) a nat. trans. (\(I\) identity functor ; η::a → M a)
    • +
    +

    Satisfying

    +
      +
    • \(M ⊙ (M ⊙ M) = (M ⊙ M) ⊙ M\)
    • +
    • \(η ⊙ M = M = M ⊙ η\)
    • +
    +
    +
    +

    Compare with Monoid

    +

    A Monoid is a triplet \((E,∙,e)\) s.t.

    +
      +
    • \(E\) a set
    • +
    • \(∙:E×E→E\)
    • +
    • \(e:1→E\)
    • +
    +

    Satisfying

    +
      +
    • \(x∙(y∙z) = (x∙y)∙z, ∀x,y,z∈E\)
    • +
    • \(e∙x = x = x∙e, ∀x∈E\)
    • +
    +
    +
    +

    Monads are just Monoids

    +
    +

    A Monad is just a monoid in the category of endofunctors, what's the problem?

    +
    +

    The real sentence was:

    +
    +

    All told, a monad in X is just a monoid in the category of endofunctors of X, with product × replaced by composition of endofunctors and unit set by the identity endofunctor.

    +
    +
    +
    +

    Example: List

    +
      +
    • [] :: * -> * an Endofunctor
    • +
    • \(⊙:M×M→M\) a nat. trans. (join :: M (M a) -> M a)
    • +
    • \(η:I→M\) a nat. trans.
    • +
    +
    -- In Haskell ⊙ is "join" in "Control.Monad"
    +join :: [[a]] -> [a]
    +join = concat
    +
    +-- In Haskell the "return" function (unfortunate name)
    +η :: a -> [a]
    +η x = [x]
    + +
    +
    +

    Example: List (law verification)

    +

    Example: List is a functor (join is ⊙)

    +
      +
    • \(M ⊙ (M ⊙ M) = (M ⊙ M) ⊙ M\)
    • +
    • \(η ⊙ M = M = M ⊙ η\)
    • +
    +
    join [ join [[x,y,...,z]] ] = join [[x,y,...,z]]
    +                            = join (join [[[x,y,...,z]]])
    +join (η [x]) = [x] = join [η x]
    + +

    Therefore ([],join,η) is a monad.

    +
    +
    +

    Monads useful?

    +

    A LOT of monad tutorial on the net. Just one example; the State Monad

    +

    DrawScene to State Screen DrawScene ; still pure.

    +
    main = drawImage (width,height)
    +
    +drawImage :: Screen -> DrawScene
    +drawImage screen = do
    +    drawPoint p screen
    +    drawCircle c screen
    +    drawRectangle r screen
    +
    +drawPoint point screen = ...
    +drawCircle circle screen = ...
    +drawRectangle rectangle screen = ...
    +
    main = do
    +    put (Screen 1024 768)
    +    drawImage
    +
    +drawImage :: State Screen DrawScene
    +drawImage = do
    +    drawPoint p
    +    drawCircle c
    +    drawRectangle r
    +
    +drawPoint :: Point ->
    +               State Screen DrawScene
    +drawPoint p = do
    +    Screen width height <- get
    +    ...
    +
    +
    +

    fold

    +fold +
    +
    +

    κατα-morphism

    +catamorphism +
    +
    +

    κατα-morphism: fold generalization

    +

    acc type of the "accumulator":
    fold :: (acc -> a -> acc) -> acc -> [a] -> acc

    +

    Idea: put the accumulated value inside the type.

    +
    -- Equivalent to fold (+1) 0 "cata"
    +(Cons 'c' (Cons 'a' (Cons 't' (Cons 'a' Nil))))
    +(Cons 'c' (Cons 'a' (Cons 't' (Cons 'a' 0))))
    +(Cons 'c' (Cons 'a' (Cons 't' 1)))
    +(Cons 'c' (Cons 'a' 2))
    +(Cons 'c' 3)
    +4
    + +

    But where are all the informations? (+1) and 0?

    +
    +
    +

    κατα-morphism: Missing Information

    +

    Where is the missing information?

    +
      +
    • Functor operator fmap
    • +
    • Algebra representing the (+1) and also knowing about the 0.
    • +
    +

    First example, make length on [Char]

    +
    +
    +

    κατα-morphism: Type work

    +
    
    +data StrF a = Cons Char a | Nil
    +data Str' = StrF Str'
    +
    +-- generalize the construction of Str to other datatype
    +-- Mu: type fixed point
    +-- Mu :: (* -> *) -> *
    +
    +data Mu f = InF { outF :: f (Mu f) }
    +data Str = Mu StrF
    +
    +-- Example
    +foo=InF { outF = Cons 'f'
    +        (InF { outF = Cons 'o'
    +            (InF { outF = Cons 'o'
    +                (InF { outF = Nil })})})}
    + +
    +
    +

    κατα-morphism: missing information retrieved

    +
    type Algebra f a = f a -> a
    +instance Functor (StrF a) =
    +    fmap f (Cons c x) = Cons c (f x)
    +    fmap _ Nil = Nil
    + +
    cata :: Functor f => Algebra f a -> Mu f -> a
    +cata f = f . fmap (cata f) . outF
    + +
    +
    +

    κατα-morphism: Finally length

    +

    All needed information for making length.

    +
    instance Functor (StrF a) =
    +    fmap f (Cons c x) = Cons c (f x)
    +    fmap _ Nil = Nil
    +
    +length' :: Str -> Int
    +length' = cata phi where
    +    phi :: Algebra StrF Int -- StrF Int -> Int
    +    phi (Cons a b) = 1 + b
    +    phi Nil = 0
    +
    +main = do
    +    l <- length' $ stringToStr "Toto"
    +    ...
    +
    +
    +

    κατα-morphism: extension to Trees

    +

    Once you get the trick, it is easy to extent to most Functor.

    +
    type Tree = Mu TreeF
    +data TreeF x = Node Int [x]
    +
    +instance Functor TreeF where
    +  fmap f (Node e xs) = Node e (fmap f xs)
    +
    +depth = cata phi where
    +  phi :: Algebra TreeF Int -- TreeF Int -> Int
    +  phi (Node x sons) = 1 + foldr max 0 sons
    +
    +
    +

    Conclusion

    +

    Category Theory oriented Programming:

    +
      +
    • Focus on the type and operators
    • +
    • Extreme generalisation
    • +
    • Better modularity
    • +
    • Better control through properties of types
    • +
    +

    No cat were harmed in the making of this presentation.

    +
    +]]>
    + + + Un example progressif avec Haskell + + http://yannesposito.com/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/index.html + 2012-06-15T00:00:00Z + 2012-06-15T00:00:00Z + The B in Benoît B. Mandelbrot stand for Benoît B. Mandelbrot

    +
    + +

    tlpl: Un exemple progressif d’utilisation d’Haskell. Vous pourrez voir un ensemble de Mandelbrot étendu à la troisième dimension. De plus le code sera très propre. Les détails de rendu sont séparés dans un module externe. Le code descriptif intéressant est concentré dans un environnement pur et fonctionnel. Vous pouvez vous inspirer de ce code utilisant le paradigme fonctional dans tous les languages.

    +
    + +

    Introduction

    +

    In my preceding article I introduced Haskell.

    +

    This article goes further. It will show how to use functional programming with interactive programs. But more than that, it will show how to organize your code in a functional way. This article is more about functional paradigm than functional language. The code organization can be used in most imperative language.

    +

    As Haskell is designed for functional paradigm, it is easier to use in this context. In reality, the firsts sections will use an imperative paradigm. As you can use functional paradigm in imperative language, you can also use imperative paradigm in functional languages.

    +

    This article is about creating an useful and clean program. It can interact with the user in real time. It uses OpenGL, a library with imperative programming foundations. Despite this fact, most of the final code will remain in the pure part (no IO).

    +

    I believe the main audience for this article are:

    +
      +
    • Haskell programmer looking for an OpengGL tutorial.
    • +
    • People interested in program organization (programming language agnostic).
    • +
    • Fractal lovers and in particular 3D fractal.
    • +
    • People interested in user interaction in a functional paradigm.
    • +
    +

    I had in mind for some time now to make a Mandelbrot set explorer. I had already written a command line Mandelbrot set generator in Haskell. This utility is highly parallel; it uses the repa package1.

    +

    This time, we will not parallelize the computation. Instead, we will display the Mandelbrot set extended in 3D using OpenGL and Haskell. You will be able to move it using your keyboard. This object is a Mandelbrot set in the plan (z=0), and something nice to see in 3D.

    +

    Here are some screenshots of the result:

    +
    +The entire Mandelbulb +
    +The entire Mandelbulb +
    +
    +A Mandelbulb detail +
    +A Mandelbulb detail +
    +
    +Another detail of the Mandelbulb +
    +Another detail of the Mandelbulb +
    + +

    And you can see the intermediate steps to reach this goal:

    +

    The parts of the article

    +

    From the 2nd section to the 4th it will be dirtier and dirtier. We start cleaning the code at the 5th section.

    +
    +

    Download the source code of this section → 01_Introduction/hglmandel.lhs

    +

    First version

    +

    We can consider two parts. The first being mostly some boilerplate2. And the second part more focused on OpenGL and content.

    +

    Let’s play the song of our people

    +
    +
    import Graphics.Rendering.OpenGL
    +import Graphics.UI.GLUT
    +import Data.IORef
    +
    + +

    For efficiency reason3, I will not use the default Haskell Complex data type.

    +
    +
    data Complex = C (Float,Float) deriving (Show,Eq)
    +
    + +
    +
    instance Num Complex where
    +    fromInteger n = C (fromIntegral n,0.0)
    +    C (x,y) * C (z,t) = C (z*x - y*t, y*z + x*t)
    +    C (x,y) + C (z,t) = C (x+z, y+t)
    +    abs (C (x,y))     = C (sqrt (x*x + y*y),0.0)
    +    signum (C (x,y))  = C (signum x , 0.0)
    +
    + +

    We declare some useful functions for manipulating complex numbers:

    +
    +
    complex :: Float -> Float -> Complex
    +complex x y = C (x,y)
    +
    +real :: Complex -> Float
    +real (C (x,y))    = x
    +
    +im :: Complex -> Float
    +im   (C (x,y))    = y
    +
    +magnitude :: Complex -> Float
    +magnitude = real.abs
    +
    + +

    Let us start

    +

    We start by giving the main architecture of our program:

    +
    +
    main :: IO ()
    +main = do
    +  -- GLUT need to be initialized
    +  (progname,_) <- getArgsAndInitialize
    +  -- We will use the double buffered mode (GL constraint)
    +  initialDisplayMode $= [DoubleBuffered]
    +  -- We create a window with some title
    +  createWindow "Mandelbrot Set with Haskell and OpenGL"
    +  -- Each time we will need to update the display
    +  -- we will call the function 'display'
    +  displayCallback $= display
    +  -- We enter the main loop
    +  mainLoop
    +
    + +

    Mainly, we initialize our OpenGL application. We declared that the function display will be used to render the graphics:

    +
    +
    display = do
    +  clear [ColorBuffer] -- make the window black
    +  loadIdentity -- reset any transformation
    +  preservingMatrix drawMandelbrot
    +  swapBuffers -- refresh screen
    +
    + +

    Also here, there is only one interesting line; the draw will occur in the function drawMandelbrot.

    +

    This function will provide a list of draw actions. Remember that OpenGL is imperative by design. Then, one of the consequence is you must write the actions in the right order. No easy parallel drawing here. Here is the function which will render something on the screen:

    +
    +
    drawMandelbrot =
    +  -- We will print Points (not triangles for example) 
    +  renderPrimitive Points $ do
    +    mapM_ drawColoredPoint allPoints
    +  where
    +      drawColoredPoint (x,y,c) = do
    +          color c -- set the current color to c
    +          -- then draw the point at position (x,y,0)
    +          -- remember we're in 3D
    +          vertex $ Vertex3 x y 0 
    +
    + +

    The mapM_ function is mainly the same as map but inside a monadic context. More precisely, this can be transformed as a list of actions where the order is important:

    +
    drawMandelbrot = 
    +  renderPrimitive Points $ do
    +    color color1
    +    vertex $ Vertex3 x1 y1 0
    +    ...
    +    color colorN
    +    vertex $ Vertex3 xN yN 0
    +

    We also need some kind of global variables. In fact, global variable are a proof of a design problem. We will get rid of them later.

    +
    +
    width = 320 :: GLfloat
    +height = 320 :: GLfloat
    +
    + +

    And of course our list of colored points. In OpenGL the default coordinate are from -1 to 1.

    +
    +
    allPoints :: [(GLfloat,GLfloat,Color3 GLfloat)]
    +allPoints = [ (x/width,y/height,colorFromValue $ mandel x y) | 
    +                  x <- [-width..width], 
    +                  y <- [-height..height]]
    +
    + +

    We need a function which transform an integer value to some color:

    +
    +
    colorFromValue n =
    +  let 
    +      t :: Int -> GLfloat
    +      t i = 0.5 + 0.5*cos( fromIntegral i / 10 )
    +  in
    +    Color3 (t n) (t (n+5)) (t (n+10))
    +
    + +

    And now the mandel function. Given two coordinates in pixels, it returns some integer value:

    +
    +
    mandel x y = 
    +  let r = 2.0 * x / width
    +      i = 2.0 * y / height
    +  in
    +      f (complex r i) 0 64
    +
    + +

    It uses the main Mandelbrot function for each complex \(c\). The Mandelbrot set is the set of complex number \(c\) such that the following sequence does not escape to infinity.

    +

    Let us define \(f_c: \)

    +


    fc(z) = z2 + c

    +

    The sequence is:

    +


    0 → fc(0) → fc(fc(0)) → ⋯ → fcn(0) → ⋯

    +

    Of course, instead of trying to test the real limit, we just make a test after a finite number of occurrences.

    +
    +
    f :: Complex -> Complex -> Int -> Int
    +f c z 0 = 0
    +f c z n = if (magnitude z > 2 ) 
    +          then n
    +          else f c ((z*z)+c) (n-1)
    +
    + +

    Well, if you download this file (look at the bottom of this section), compile it and run it this is the result:

    +

    The mandelbrot set version 1

    +

    A first very interesting property of this program is that the computation for all the points is done only once. It is a bit long before the first image appears, but if you resize the window, it updates instantaneously. This property is a direct consequence of purity. If you look closely, you see that allPoints is a pure list. Therefore, calling allPoints will always render the same result and Haskell is clever enough to use this property. While Haskell doesn’t garbage collect allPoints the result is reused for free. We did not specified this value should be saved for later use. It is saved for us.

    +

    See what occurs if we make the window bigger:

    +

    The mandelbrot too wide, black lines and columns

    +

    We see some black lines because we have drawn less point than there is on the surface. We can repair this by drawing little squares instead of just points. But, instead we will do something a bit different and unusual.

    +

    Download the source code of this section → 01_Introduction/hglmandel.lhs

    +
    +

    Download the source code of this section → 02_Edges/HGLMandelEdge.lhs

    +

    Only the edges

    +
    + +
    +
    import Graphics.Rendering.OpenGL
    +import Graphics.UI.GLUT
    +import Data.IORef
    +-- Use UNPACK data because it is faster
    +-- The ! is for strict instead of lazy
    +data Complex = C  {-# UNPACK #-} !Float 
    +                  {-# UNPACK #-} !Float 
    +               deriving (Show,Eq)
    +instance Num Complex where
    +    fromInteger n = C (fromIntegral n) 0.0
    +    (C x y) * (C z t) = C (z*x - y*t) (y*z + x*t)
    +    (C x y) + (C z t) = C (x+z) (y+t)
    +    abs (C x y)     = C (sqrt (x*x + y*y)) 0.0
    +    signum (C x y)  = C (signum x) 0.0
    +complex :: Float -> Float -> Complex
    +complex x y = C x y
    +
    +real :: Complex -> Float
    +real (C x y)    = x
    +
    +im :: Complex -> Float
    +im   (C x y)    = y
    +
    +magnitude :: Complex -> Float
    +magnitude = real.abs
    +main :: IO ()
    +main = do
    +  -- GLUT need to be initialized
    +  (progname,_) <- getArgsAndInitialize
    +  -- We will use the double buffered mode (GL constraint)
    +  initialDisplayMode $= [DoubleBuffered]
    +  -- We create a window with some title
    +  createWindow "Mandelbrot Set with Haskell and OpenGL"
    +  -- Each time we will need to update the display
    +  -- we will call the function 'display'
    +  displayCallback $= display
    +  -- We enter the main loop
    +  mainLoop
    +display = do
    +   -- set the background color (dark solarized theme)
    +  clearColor $= Color4 0 0.1686 0.2117 1
    +  clear [ColorBuffer] -- make the window black
    +  loadIdentity -- reset any transformation
    +  preservingMatrix drawMandelbrot
    +  swapBuffers -- refresh screen
    +
    +width = 320 :: GLfloat
    +height = 320 :: GLfloat
    +
    + +
    + +

    This time, instead of drawing all points, we will simply draw the edges of the Mandelbrot set. The method I use is a rough approximation. I consider the Mandelbrot set to be almost convex. The result will be good enough for the purpose of this tutorial.

    +

    We change slightly the drawMandelbrot function. We replace the Points by LineLoop

    +
    +
    drawMandelbrot =
    +  -- We will print Points (not triangles for example) 
    +  renderPrimitive LineLoop $ do
    +    mapM_ drawColoredPoint allPoints
    +  where
    +      drawColoredPoint (x,y,c) = do
    +          color c -- set the current color to c
    +          -- then draw the point at position (x,y,0)
    +          -- remember we're in 3D
    +          vertex $ Vertex3 x y 0 
    +
    + +

    And now, we should change our list of points. Instead of drawing every point of the visible surface, we will choose only point on the surface.

    +
    +
    allPoints = positivePoints ++ 
    +      map (\(x,y,c) -> (x,-y,c)) (reverse positivePoints)
    +
    + +

    We only need to compute the positive point. The Mandelbrot set is symmetric relatively to the abscisse axis.

    +
    +
    positivePoints :: [(GLfloat,GLfloat,Color3 GLfloat)]
    +positivePoints = do
    +     x <- [-width..width]
    +     let y = maxZeroIndex (mandel x) 0 height (log2 height)
    +     if y < 1 -- We don't draw point in the absciss
    +        then []
    +        else return (x/width,y/height,colorFromValue $ mandel x y)
    +     where
    +         log2 n = floor ((log n) / log 2)
    +
    + +

    This function is interesting. For those not used to the list monad here is a natural language version of this function:

    +
    positivePoints =
    +    for all x in the range [-width..width]
    +    let y be smallest number s.t. mandel x y > 0
    +    if y is on 0 then don't return a point
    +    else return the value corresonding to (x,y,color for (x+iy))
    +

    In fact using the list monad you write like if you consider only one element at a time and the computation is done non deterministically. To find the smallest number such that mandel x y > 0 we use a simple dichotomy:

    +
    +
    -- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex func minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if (func medpoint) /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    + +

    No rocket science here. See the result now:

    +

    The edges of the mandelbrot set

    +
    + +
    +
    colorFromValue n =
    +  let 
    +      t :: Int -> GLfloat
    +      t i = 0.5 + 0.5*cos( fromIntegral i / 10 )
    +  in
    +    Color3 (t n) (t (n+5)) (t (n+10))
    +
    + +
    +
    mandel x y = 
    +  let r = 2.0 * x / width
    +      i = 2.0 * y / height
    +  in
    +      f (complex r i) 0 64
    +
    + +
    +
    f :: Complex -> Complex -> Int -> Int
    +f c z 0 = 0
    +f c z n = if (magnitude z > 2 ) 
    +          then n
    +          else f c ((z*z)+c) (n-1)
    +
    + +
    + +

    Download the source code of this section → 02_Edges/HGLMandelEdge.lhs

    +
    +

    Download the source code of this section → 03_Mandelbulb/Mandelbulb.lhs

    +

    3D Mandelbrot?

    +

    Now we will we extend to a third dimension. But, there is no 3D equivalent to complex. In fact, the only extension known are quaternions (in 4D). As I know almost nothing about quaternions, I will use some extended complex, instead of using a 3D projection of quaternions. I am pretty sure this construction is not useful for numbers. But it will be enough for us to create something that look nice.

    +

    This section is quite long, but don’t be afraid, most of the code is some OpenGL boilerplate. If you just want to skim this section, here is a high level representation:

    +
    +
      +
    • OpenGL Boilerplate

    • +
    • set some IORef (understand variables) for states
    • +
    • Drawing:

      +
        +
      • set doubleBuffer, handle depth, window size…
      • +
      • Use state to apply some transformations
      • +
    • +
    • Keyboard: hitting some key change the state of IORef

    • +
    • Generate 3D Object

    • +
    +

    ~ allPoints :: [ColoredPoint]
    allPoints = for all (x,y), -width 0 add the points (x, y, z,color) (x,-y, z,color) (x, y,-z,color) (x,-y,-z,color) + neighbors to make triangles ~

    +
    +
    + +
    +
    import Graphics.Rendering.OpenGL
    +import Graphics.UI.GLUT
    +import Data.IORef
    +type ColoredPoint = (GLfloat,GLfloat,GLfloat,Color3 GLfloat)
    +
    + +
    + +

    We declare a new type ExtComplex (for extended complex). An extension of complex numbers with a third component:

    +
    +
    data ExtComplex = C (GLfloat,GLfloat,GLfloat) 
    +                  deriving (Show,Eq)
    +instance Num ExtComplex where
    +    -- The shape of the 3D mandelbrot 
    +    -- will depend on this formula
    +    C (x,y,z) * C (x',y',z') = C (x*x' - y*y' - z*z', 
    +                                  x*y' + y*x' + z*z', 
    +                                  x*z' + z*x' )
    +    -- The rest is straightforward
    +    fromInteger n = C (fromIntegral n, 0, 0)
    +    C (x,y,z) + C (x',y',z') = C (x+x', y+y', z+z')
    +    abs (C (x,y,z))     = C (sqrt (x*x + y*y + z*z), 0, 0)
    +    signum (C (x,y,z))  = C (signum x, signum y, signum z)
    +
    + +

    The most important part is the new multiplication instance. Modifying this formula will change radically the shape of the result. Here is the formula written in a more mathematical notation. I called the third component of these extended complex strange.

    +


    real((x, y, z) * (xʹ, yʹ, zʹ)) = xxʹ − yyʹ − zzʹ

    +


    im((x, y, z) * (xʹ, yʹ, zʹ)) = xyʹ − yxʹ + zzʹ

    +


    strange((x, y, z) * (xʹ, yʹ, zʹ)) = xzʹ + zxʹ

    +

    Note how if z=z'=0 then the multiplication is the same to the complex one.

    +
    + +
    +
    extcomplex :: GLfloat -> GLfloat -> GLfloat -> ExtComplex
    +extcomplex x y z = C (x,y,z)
    +
    +real :: ExtComplex -> GLfloat
    +real (C (x,y,z))    = x
    +
    +im :: ExtComplex -> GLfloat
    +im   (C (x,y,z))    = y
    +
    +strange :: ExtComplex -> GLfloat
    +strange (C (x,y,z)) = z
    +
    +magnitude :: ExtComplex -> GLfloat
    +magnitude = real.abs
    +
    + +
    + +

    From 2D to 3D

    +

    As we will use some 3D, we add some new directive in the boilerplate. But mainly, we simply state that will use some depth buffer. And also we will listen the keyboard.

    +
    +
    main :: IO ()
    +main = do
    +  -- GLUT need to be initialized
    +  (progname,_) <- getArgsAndInitialize
    +  -- We will use the double buffered mode (GL constraint)
    +  -- We also Add the DepthBuffer (for 3D)
    +  initialDisplayMode $= 
    +      [WithDepthBuffer,DoubleBuffered,RGBMode]
    +  -- We create a window with some title
    +  createWindow "3D HOpengGL Mandelbrot"
    +  -- We add some directives
    +  depthFunc  $= Just Less
    +  windowSize $= Size 500 500
    +  -- Some state variables (I know it feels BAD)
    +  angle   <- newIORef ((35,0)::(GLfloat,GLfloat))
    +  zoom    <- newIORef (2::GLfloat)
    +  campos  <- newIORef ((0.7,0)::(GLfloat,GLfloat))
    +  -- Function to call each frame
    +  idleCallback $= Just idle
    +  -- Function to call when keyboard or mouse is used
    +  keyboardMouseCallback $= 
    +          Just (keyboardMouse angle zoom campos)
    +  -- Each time we will need to update the display
    +  -- we will call the function 'display'
    +  -- But this time, we add some parameters
    +  displayCallback $= display angle zoom campos
    +  -- We enter the main loop
    +  mainLoop
    +
    + +

    The idle is here to change the states. There should never be any modification done in the display function.

    +
    +
    idle = postRedisplay Nothing
    +
    + +

    We introduce some helper function to manipulate standard IORef. Mainly modVar x f is equivalent to the imperative x:=f(x), modFst (x,y) (+1) is equivalent to (x,y) := (x+1,y) and modSnd (x,y) (+1) is equivalent to (x,y) := (x,y+1)

    +
    +
    modVar v f = do
    +  v' <- get v
    +  v $= (f v')
    +mapFst f (x,y) = (f x,  y)
    +mapSnd f (x,y) = (  x,f y)
    +
    + +

    And we use them to code the function handling keyboard. We will use the keys hjkl to rotate, oi to zoom and sedf to move. Also, hitting space will reset the view. Remember that angle and campos are pairs and zoom is a scalar. Also note (+0.5) is the function \x->x+0.5 and (-0.5) is the number -0.5 (yes I share your pain).

    +
    +
    keyboardMouse angle zoom campos key state modifiers position =
    +  -- We won't use modifiers nor position
    +  kact angle zoom campos key state
    +  where 
    +    -- reset view when hitting space
    +    kact a z p (Char ' ') Down = do
    +          a $= (0,0) -- angle 
    +          z $= 1     -- zoom
    +          p $= (0,0) -- camera position
    +    -- use of hjkl to rotate
    +    kact a _ _ (Char 'h') Down = modVar a (mapFst (+0.5))
    +    kact a _ _ (Char 'l') Down = modVar a (mapFst (+(-0.5)))
    +    kact a _ _ (Char 'j') Down = modVar a (mapSnd (+0.5))
    +    kact a _ _ (Char 'k') Down = modVar a (mapSnd (+(-0.5)))
    +    -- use o and i to zoom
    +    kact _ z _ (Char 'o') Down = modVar z (*1.1)
    +    kact _ z _ (Char 'i') Down = modVar z (*0.9)
    +    -- use sdfe to move the camera
    +    kact _ _ p (Char 's') Down = modVar p (mapFst (+0.1))
    +    kact _ _ p (Char 'f') Down = modVar p (mapFst (+(-0.1)))
    +    kact _ _ p (Char 'd') Down = modVar p (mapSnd (+0.1))
    +    kact _ _ p (Char 'e') Down = modVar p (mapSnd (+(-0.1)))
    +    -- any other keys does nothing
    +    kact _ _ _ _ _ = return ()
    +
    + +

    Note display takes some parameters this time. This function if full of boilerplate:

    +
    +
    display angle zoom position = do
    +   -- set the background color (dark solarized theme)
    +  clearColor $= Color4 0 0.1686 0.2117 1
    +  clear [ColorBuffer,DepthBuffer]
    +  -- Transformation to change the view
    +  loadIdentity -- reset any transformation
    +  -- tranlate
    +  (x,y) <- get position
    +  translate $ Vector3 x y 0 
    +  -- zoom
    +  z <- get zoom
    +  scale z z z
    +  -- rotate
    +  (xangle,yangle) <- get angle
    +  rotate xangle $ Vector3 1.0 0.0 (0.0::GLfloat)
    +  rotate yangle $ Vector3 0.0 1.0 (0.0::GLfloat)
    +
    +  -- Now that all transformation were made
    +  -- We create the object(s)
    +  preservingMatrix drawMandelbrot
    +
    +  swapBuffers -- refresh screen
    +
    + +

    Not much to say about this function. Mainly there are two parts: apply some transformations, draw the object.

    +

    The 3D Mandelbrot

    +

    We have finished with the OpenGL section, let’s talk about how we generate the 3D points and colors. First, we will set the number of details to 200 pixels in the three dimensions.

    +
    +
    nbDetails = 200 :: GLfloat
    +width  = nbDetails
    +height = nbDetails
    +deep   = nbDetails
    +
    + +

    This time, instead of just drawing some line or some group of points, we will show triangles. The function allPoints will provide a multiple of three points. Each three successive point representing the coordinate of each vertex of a triangle.

    +
    +
    drawMandelbrot = do
    +  -- We will print Points (not triangles for example) 
    +  renderPrimitive Triangles $ do
    +    mapM_ drawColoredPoint allPoints
    +  where
    +      drawColoredPoint (x,y,z,c) = do
    +          color c
    +          vertex $ Vertex3 x y z
    +
    + +

    In fact, we will provide six ordered points. These points will be used to draw two triangles.

    +

    Explain triangles

    +

    The next function is a bit long. Here is an approximative English version:

    +
    forall x from -width to width
    +  forall y from -height to height
    +    forall the neighbors of (x,y)
    +      let z be the smalled depth such that (mandel x y z)>0
    +      let c be the color given by mandel x y z 
    +      add the point corresponding to (x,y,z,c)
    +

    Also, I added a test to hide points too far from the border. In fact, this function show points close to the surface of the modified mandelbrot set. But not the mandelbrot set itself.

    +
    depthPoints :: [ColoredPoint]
    +depthPoints = do
    +  x <- [-width..width]
    +  y <- [-height..height]
    +  let 
    +      depthOf x' y' = maxZeroIndex (mandel x' y') 0 deep logdeep 
    +      logdeep = floor ((log deep) / log 2)
    +      z1 = depthOf    x     y
    +      z2 = depthOf (x+1)    y
    +      z3 = depthOf (x+1) (y+1)
    +      z4 = depthOf    x  (y+1)
    +      c1 = mandel    x     y  (z1+1)
    +      c2 = mandel (x+1)    y  (z2+1)
    +      c3 = mandel (x+1) (y+1) (z3+1)
    +      c4 = mandel    x  (y+1) (z4+1)
    +      p1 = (   x /width,   y /height, z1/deep, colorFromValue c1)
    +      p2 = ((x+1)/width,   y /height, z2/deep, colorFromValue c2)
    +      p3 = ((x+1)/width,(y+1)/height, z3/deep, colorFromValue c3)
    +      p4 = (   x /width,(y+1)/height, z4/deep, colorFromValue c4)
    +  if (and $ map (>=57) [c1,c2,c3,c4])
    +  then []
    +  else [p1,p2,p3,p1,p3,p4]
    +

    If you look at the function above, you see a lot of common patterns. Haskell is very efficient to make this better. Here is a harder to read but shorter and more generic rewritten function:

    +
    +
    depthPoints :: [ColoredPoint]
    +depthPoints = do
    +  x <- [-width..width]
    +  y <- [-height..height]
    +  let 
    +    neighbors = [(x,y),(x+1,y),(x+1,y+1),(x,y+1)]
    +    depthOf (u,v) = maxZeroIndex (mandel u v) 0 deep logdeep
    +    logdeep = floor ((log deep) / log 2)
    +    -- zs are 3D points with found depth
    +    zs = map (\(u,v) -> (u,v,depthOf (u,v))) neighbors
    +    -- ts are 3D pixels + mandel value
    +    ts = map (\(u,v,w) -> (u,v,w,mandel u v (w+1))) zs
    +    -- ps are 3D opengl points + color value
    +    ps = map (\(u,v,w,c') -> 
    +        (u/width,v/height,w/deep,colorFromValue c')) ts
    +  -- If the point diverged too fast, don't display it
    +  if (and $ map (\(_,_,_,c) -> c>=57) ts)
    +  then []
    +  -- Draw two triangles
    +  else [ps!!0,ps!!1,ps!!2,ps!!0,ps!!2,ps!!3]
    +
    + +

    If you prefer the first version, then just imagine how hard it will be to change the enumeration of the point from (x,y) to (x,z) for example.

    +

    Also, we didn’t searched for negative values. This modified Mandelbrot is no more symmetric relatively to the plan y=0. But it is symmetric relatively to the plan z=0. Then I mirror these values.

    +
    +
    allPoints :: [ColoredPoint]
    +allPoints = planPoints ++ map inverseDepth  planPoints
    +  where 
    +      planPoints = depthPoints
    +      inverseDepth (x,y,z,c) = (x,y,-z+1/deep,c)
    +
    + +

    The rest of the program is very close to the preceding one.

    +
    + +
    +
    -- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => 
    +                 (a -> b) -> a -> a -> Int -> a
    +maxZeroIndex func minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if (func medpoint) /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    + +

    I made the color slightly brighter

    +
    +
    colorFromValue n =
    +  let 
    +      t :: Int -> GLfloat
    +      t i = 0.7 + 0.3*cos( fromIntegral i / 10 )
    +  in
    +    Color3 (t n) (t (n+5)) (t (n+10))
    +
    + +

    We only changed from Complex to ExtComplex of the main f function.

    +
    +
    f :: ExtComplex -> ExtComplex -> Int -> Int
    +f c z 0 = 0
    +f c z n = if (magnitude z > 2 ) 
    +          then n
    +          else f c ((z*z)+c) (n-1)
    +
    + +
    + +

    We simply add a new dimension to the mandel function and change the type signature of f from Complex to ExtComplex.

    +
    +
    mandel x y z = 
    +  let r = 2.0 * x / width
    +      i = 2.0 * y / height
    +      s = 2.0 * z / deep
    +  in
    +      f (extcomplex r i s) 0 64
    +
    + +

    Here is the result:

    +

    A 3D mandelbrot like

    +

    Download the source code of this section → 03_Mandelbulb/Mandelbulb.lhs

    +
    +

    Download the source code of this section → 04_Mandelbulb/Mandelbulb.lhs

    +

    Naïve code cleaning

    +

    The first approach to clean the code is to separate the GLUT/OpenGL part from the computation of the shape. Here is the cleaned version of the preceding section. Most boilerplate was put in external files.

    + +
    +
    import YBoiler -- Most the OpenGL Boilerplate
    +import Mandel -- The 3D Mandelbrot maths
    +
    + +

    The yMainLoop takes two arguments: the title of the window and a function from time to triangles

    +
    +
    main :: IO ()
    +main = yMainLoop "3D Mandelbrot" (\_ -> allPoints)
    +
    + +

    We set some global constant (this is generally bad).

    +
    +
    nbDetails = 200 :: GLfloat
    +width  = nbDetails
    +height = nbDetails
    +deep   = nbDetails
    +
    + +

    We then generate colored points from our function. This is similar to the preceding section.

    +
    +
    allPoints :: [ColoredPoint]
    +allPoints = planPoints ++ map inverseDepth  planPoints
    +  where 
    +      planPoints = depthPoints ++ map inverseHeight depthPoints
    +      inverseHeight (x,y,z,c) = (x,-y,z,c)
    +      inverseDepth (x,y,z,c) = (x,y,-z+1/deep,c)
    +
    + +
    +
    depthPoints :: [ColoredPoint]
    +depthPoints = do
    +  x <- [-width..width]
    +  y <- [0..height]
    +  let 
    +    neighbors = [(x,y),(x+1,y),(x+1,y+1),(x,y+1)]
    +    depthOf (u,v) = maxZeroIndex (ymandel u v) 0 deep 7
    +    -- zs are 3D points with found depth
    +    zs = map (\(u,v) -> (u,v,depthOf (u,v))) neighbors
    +    -- ts are 3D pixels + mandel value
    +    ts = map (\(u,v,w) -> (u,v,w,ymandel u v (w+1))) zs
    +    -- ps are 3D opengl points + color value
    +    ps = map (\(u,v,w,c') -> 
    +        (u/width,v/height,w/deep,colorFromValue c')) ts
    +  -- If the point diverged too fast, don't display it
    +  if (and $ map (\(_,_,_,c) -> c>=57) ts)
    +  then []
    +  -- Draw two triangles
    +  else [ps!!0,ps!!1,ps!!2,ps!!0,ps!!2,ps!!3]
    +
    +-- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex func minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if (func medpoint) /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    +colorFromValue n =
    +  let 
    +      t :: Int -> GLfloat
    +      t i = 0.7 + 0.3*cos( fromIntegral i / 10 )
    +  in
    +    ((t n),(t (n+5)),(t (n+10)))
    +
    +ymandel x y z = mandel (2*x/width) (2*y/height) (2*z/deep) 64
    +
    + +

    This code is cleaner but many things doesn’t feel right. First, all the user interaction code is outside our main file. I feel it is okay to hide the detail for the rendering. But I would have preferred to control the user actions.

    +

    On the other hand, we continue to handle a lot rendering details. For example, we provide ordered vertices.

    +

    Download the source code of this section → 04_Mandelbulb/Mandelbulb.lhs

    +
    +

    Download the source code of this section → 05_Mandelbulb/Mandelbulb.lhs

    +

    Functional organization?

    +

    Some points:

    +
      +
    1. OpenGL and GLUT is done in C. In particular the mainLoop function is a direct link to the C library (FFI). This function is clearly far from the functional paradigm. Could we make this better? We will have two choices:
    2. +
    +
      +
    • create our own mainLoop function to make it more functional.
    • +
    • deal with the imperative nature of the GLUT mainLoop function.
    • +
    +

    As one of the goal of this article is to understand how to deal with existing libraries and particularly the one coming from imperative languages, we will continue to use the mainLoop function. 2. Our main problem come from user interaction. If you ask “the Internet”, about how to deal with user interaction with a functional paradigm, the main answer is to use functional reactive programming (FRP). I won’t use FRP in this article. Instead, I’ll use a simpler while less effective way to deal with user interaction. But The method I’ll use will be as pure and functional as possible.

    +

    Here is how I imagine things should go. First, what the main loop should look like if we could make our own:

    +
    functionalMainLoop =
    +    Read user inputs and provide a list of actions
    +    Apply all actions to the World
    +    Display one frame 
    +    repetere aeternum
    +

    Clearly, ideally we should provide only three parameters to this main loop function:

    +
      +
    • an initial World state
    • +
    • a mapping between the user interactions and functions which modify the world
    • +
    • a function taking two parameters: time and world state and render a new world without user interaction.
    • +
    +

    Here is a real working code, I’ve hidden most display functions. The YGL, is a kind of framework to display 3D functions. But it can easily be extended to many kind of representation.

    +
    +
    import YGL -- Most the OpenGL Boilerplate
    +import Mandel -- The 3D Mandelbrot maths
    +
    + +

    We first set the mapping between user input and actions. The type of each couple should be of the form (user input, f) where (in a first time) f:World -> World. It means, the user input will transform the world state.

    +
    +
    -- Centralize all user input interaction
    +inputActionMap :: InputMap World
    +inputActionMap = inputMapFromList [
    +     (Press 'k' , rotate xdir   5)
    +    ,(Press 'i' , rotate xdir (-5))
    +    ,(Press 'j' , rotate ydir   5)
    +    ,(Press 'l' , rotate ydir (-5))
    +    ,(Press 'o' , rotate zdir   5)
    +    ,(Press 'u' , rotate zdir (-5))
    +    ,(Press 'f' , translate xdir   0.1)
    +    ,(Press 's' , translate xdir (-0.1))
    +    ,(Press 'e' , translate ydir   0.1)
    +    ,(Press 'd' , translate ydir (-0.1))
    +    ,(Press 'z' , translate zdir   0.1)
    +    ,(Press 'r' , translate zdir (-0.1))
    +    ,(Press '+' , zoom    1.1)
    +    ,(Press '-' , zoom (1/1.1))
    +    ,(Press 'h' , resize    1.2)
    +    ,(Press 'g' , resize (1/1.2))
    +    ]
    +
    + +

    And of course a type design the World State. The important part is that it is our World State type. We could have used any kind of data type.

    +
    +
    -- I prefer to set my own name for these types
    +data World = World {
    +      angle       :: Point3D
    +    , scale       :: Scalar
    +    , position    :: Point3D
    +    , shape       :: Scalar -> Function3D
    +    , box         :: Box3D
    +    , told        :: Time -- last frame time
    +    } 
    +
    + +

    The important part to glue our own type to the framework is to make our type an instance of the type class DisplayableWorld. We simply have to provide the definition of some functions.

    +
    +
    instance DisplayableWorld World where
    +  winTitle _ = "The YGL Mandelbulb"
    +  camera w = Camera {
    +        camPos = position w, 
    +        camDir = angle w,
    +        camZoom = scale w }
    +  -- objects for world w
    +  -- is the list of one unique element
    +  -- The element is an YObject
    +  --   more precisely the XYFunc Function3D Box3D
    +  --   where the Function3D is the type
    +  --             Point -> Point -> Maybe (Point,Color)
    +  --   and its value here is ((shape w) res)
    +  --   and the Box3D value is defbox
    +  objects w = [XYFunc ((shape  w) res) defbox]
    +              where
    +                  res = resolution $ box w
    +                  defbox = box w
    +
    + +

    The camera function will retrieve an object of type Camera which contains most necessary information to set our camera. The objects function will returns a list of objects. Their type is YObject. Note the generation of triangles is no more in this file. Until here we only used declarative pattern.

    +

    We also need to set all our transformation functions. These function are used to update the world state.

    +
    +
    xdir :: Point3D
    +xdir = makePoint3D (1,0,0)
    +ydir :: Point3D
    +ydir = makePoint3D (0,1,0)
    +zdir :: Point3D
    +zdir = makePoint3D (0,0,1)
    +
    + +

    Note (-*<) is the scalar product (α -*< (x,y,z) = (αx,αy,αz)). Also note we could add two Point3D.

    +
    +
    rotate :: Point3D -> Scalar -> World -> World
    +rotate dir angleValue world = 
    +  world {
    +     angle = (angle world) + (angleValue -*< dir) }
    +
    +translate :: Point3D -> Scalar -> World -> World
    +translate dir len world = 
    +  world {
    +    position = (position world) + (len -*< dir) }
    +
    +zoom :: Scalar -> World -> World
    +zoom z world = world {
    +    scale = z * scale world }
    +
    +resize :: Scalar -> World -> World
    +resize r world = world {
    +    box = (box world) {
    +     resolution = sqrt ((resolution (box world))**2 * r) }}
    +
    + +

    The resize is used to generate the 3D function. As I wanted the time spent to generate a more detailed view to grow linearly I use this not so straightforward formula.

    +

    The yMainLoop takes three arguments.

    +
      +
    • A map between user Input and world transformation
    • +
    • A timed world transformation
    • +
    • An initial world state
    • +
    +
    +
    main :: IO ()
    +main = yMainLoop inputActionMap idleAction initialWorld
    +
    + +

    Here is our initial world state.

    +
    +
    -- We initialize the world state
    +-- then angle, position and zoom of the camera
    +-- And the shape function
    +initialWorld :: World
    +initialWorld = World {
    +   angle = makePoint3D (-30,-30,0)
    + , position = makePoint3D (0,0,0)
    + , scale = 0.8
    + , shape = shapeFunc 
    + , box = Box3D { minPoint = makePoint3D (-2,-2,-2)
    +               , maxPoint =  makePoint3D (2,2,2)
    +               , resolution =  0.16 }
    + , told = 0
    + }
    +
    + +

    We will define shapeFunc later. Here is the function which transform the world even without user action. Mainly it makes some rotation.

    +
    +
    idleAction :: Time -> World -> World
    +idleAction tnew world = world {
    +    angle = (angle world) + (delta -*< zdir)
    +  , told = tnew
    +  }
    +  where 
    +      anglePerSec = 5.0
    +      delta = anglePerSec * elapsed / 1000.0
    +      elapsed = fromIntegral (tnew - (told world))
    +
    + +

    Now the function which will generate points in 3D. The first parameter (res) is the resolution of the vertex generation. More precisely, res is distance between two points on one direction. We need it to “close” our shape.

    +

    The type Function3D is Point -> Point -> Maybe Point. Because we consider partial functions (for some (x,y) our function can be undefined).

    +
    +
    shapeFunc :: Scalar -> Function3D
    +shapeFunc res x y = 
    +  let 
    +      z = maxZeroIndex (ymandel x y) 0 1 20
    +  in
    +  if and [ maxZeroIndex (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 |
    +              val <- [res], xeps <- [-val,val], yeps<-[-val,val]]
    +      then Nothing 
    +      else Just (z,colorFromValue ((ymandel x y z) * 64))
    +
    + +

    With the color function.

    +
    +
    colorFromValue :: Point -> Color
    +colorFromValue n =
    +  let 
    +      t :: Point -> Scalar
    +      t i = 0.7 + 0.3*cos( i / 10 )
    +  in
    +    makeColor (t n) (t (n+5)) (t (n+10))
    +
    + +

    The rest is similar to the preceding sections.

    +
    +
    -- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => 
    +                 (a -> b) -> a -> a -> Int -> a
    +maxZeroIndex _ minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if (func medpoint) /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    +ymandel :: Point -> Point -> Point -> Point
    +ymandel x y z = fromIntegral (mandel x y z 64) / 64
    +
    + +

    I won’t explain how the magic occurs here. If you are interested, just read the file YGL.hs. It is commented a lot.

    + +

    Download the source code of this section → 05_Mandelbulb/Mandelbulb.lhs

    +
    +

    Download the source code of this section → 06_Mandelbulb/Mandelbulb.lhs

    +

    Optimization

    +

    Our code architecture feel very clean. All the meaningful code is in our main file and all display details are externalized. If you read the code of YGL.hs, you’ll see I didn’t made everything perfect. For example, I didn’t finished the code of the lights. But I believe it is a good first step and it will be easy to go further. Unfortunately the program of the preceding session is extremely slow. We compute the Mandelbulb for each frame now.

    +

    Before our program structure was:

    +
    Constant Function -> Constant List of Triangles -> Display
    +

    Now we have

    +
    Main loop -> World -> Function -> List of Objects -> Atoms -> Display
    +

    The World state could change. The compiler can no more optimize the computation for us. We have to manually explain when to redraw the shape.

    +

    To optimize we must do some things in a lower level. Mostly the program remains the same, but it will provide the list of atoms directly.

    +
    + +
    +
    import YGL -- Most the OpenGL Boilerplate
    +import Mandel -- The 3D Mandelbrot maths
    +
    +-- Centralize all user input interaction
    +inputActionMap :: InputMap World
    +inputActionMap = inputMapFromList [
    +     (Press ' ' , switchRotation)
    +    ,(Press 'k' , rotate xdir 5)
    +    ,(Press 'i' , rotate xdir (-5))
    +    ,(Press 'j' , rotate ydir 5)
    +    ,(Press 'l' , rotate ydir (-5))
    +    ,(Press 'o' , rotate zdir 5)
    +    ,(Press 'u' , rotate zdir (-5))
    +    ,(Press 'f' , translate xdir 0.1)
    +    ,(Press 's' , translate xdir (-0.1))
    +    ,(Press 'e' , translate ydir 0.1)
    +    ,(Press 'd' , translate ydir (-0.1))
    +    ,(Press 'z' , translate zdir 0.1)
    +    ,(Press 'r' , translate zdir (-0.1))
    +    ,(Press '+' , zoom 1.1)
    +    ,(Press '-' , zoom (1/1.1))
    +    ,(Press 'h' , resize 2.0)
    +    ,(Press 'g' , resize (1/2.0))
    +    ]
    +
    + +
    + +
    +
    data World = World {
    +      angle       :: Point3D
    +    , anglePerSec :: Scalar
    +    , scale       :: Scalar
    +    , position    :: Point3D
    +    , box         :: Box3D
    +    , told        :: Time 
    +    -- We replace shape by cache
    +    , cache       :: [YObject]
    +    } 
    +
    + +
    +
    instance DisplayableWorld World where
    +  winTitle _ = "The YGL Mandelbulb"
    +  camera w = Camera {
    +        camPos = position w, 
    +        camDir = angle w,
    +        camZoom = scale w }
    +  -- We update our objects instanciation
    +  objects = cache
    +
    + +
    + +
    +
    xdir :: Point3D
    +xdir = makePoint3D (1,0,0)
    +ydir :: Point3D
    +ydir = makePoint3D (0,1,0)
    +zdir :: Point3D
    +zdir = makePoint3D (0,0,1)
    +
    +rotate :: Point3D -> Scalar -> World -> World
    +rotate dir angleValue world = 
    +  world {
    +     angle = angle world + (angleValue -*< dir) }
    +
    +switchRotation :: World -> World
    +switchRotation world = 
    +  world {
    +     anglePerSec = if anglePerSec world > 0 then 0 else 5.0 }
    +
    +translate :: Point3D -> Scalar -> World -> World
    +translate dir len world = 
    +  world {
    +    position = position world + (len -*< dir) }
    +
    +zoom :: Scalar -> World -> World
    +zoom z world = world {
    +    scale = z * scale world }
    +
    + +
    +
    main :: IO ()
    +main = yMainLoop inputActionMap idleAction initialWorld
    +
    + +
    + +

    Our initial world state is slightly changed:

    +
    +
    -- We initialize the world state
    +-- then angle, position and zoom of the camera
    +-- And the shape function
    +initialWorld :: World
    +initialWorld = World {
    +   angle = makePoint3D (30,30,0)
    + , anglePerSec = 5.0
    + , position = makePoint3D (0,0,0)
    + , scale = 1.0
    + , box = Box3D { minPoint = makePoint3D (0-eps, 0-eps, 0-eps)
    +               , maxPoint = makePoint3D (0+eps, 0+eps, 0+eps)
    +               , resolution =  0.02 }
    + , told = 0
    + -- We declare cache directly this time
    + , cache = objectFunctionFromWorld initialWorld
    + }
    + where eps=2
    +
    + +

    The use of eps is a hint to make a better zoom by computing with the right bounds.

    +

    We use the YGL.getObject3DFromShapeFunction function directly. This way instead of providing XYFunc, we provide directly a list of Atoms.

    +
    +
    objectFunctionFromWorld :: World -> [YObject]
    +objectFunctionFromWorld w = [Atoms atomList]
    +  where atomListPositive = 
    +          getObject3DFromShapeFunction
    +              (shapeFunc (resolution (box w))) (box w)
    +        atomList = atomListPositive ++ 
    +          map negativeTriangle atomListPositive
    +        negativeTriangle (ColoredTriangle (p1,p2,p3,c)) = 
    +              ColoredTriangle (negz p1,negz p3,negz p2,c)
    +              where negz (P (x,y,z)) = P (x,y,-z)
    +
    + +

    We know that resize is the only world change that necessitate to recompute the list of atoms (triangles). Then we update our world state accordingly.

    +
    +
    resize :: Scalar -> World -> World
    +resize r world = 
    +  tmpWorld { cache = objectFunctionFromWorld tmpWorld }
    +  where 
    +      tmpWorld = world { box = (box world) {
    +              resolution = sqrt ((resolution (box world))**2 * r) }}
    +
    + +

    All the rest is exactly the same.

    +
    + +
    +
    idleAction :: Time -> World -> World
    +idleAction tnew world = 
    +      world {
    +        angle = angle world + (delta -*< zdir)
    +      , told = tnew
    +      }
    +  where 
    +      delta = anglePerSec world * elapsed / 1000.0
    +      elapsed = fromIntegral (tnew - (told world))
    +
    +shapeFunc :: Scalar -> Function3D
    +shapeFunc res x y = 
    +  let 
    +      z = maxZeroIndex (ymandel x y) 0 1 20
    +  in
    +  if and [ maxZeroIndex (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 |
    +              val <- [res], xeps <- [-val,val], yeps<-[-val,val]]
    +      then Nothing 
    +      else Just (z,colorFromValue 0)
    +
    +colorFromValue :: Point -> Color
    +colorFromValue n =
    +  let 
    +      t :: Point -> Scalar
    +      t i = 0.0 + 0.5*cos( i /10 )
    +  in
    +    makeColor (t n) (t (n+5)) (t (n+10))
    +
    +-- given f min max nbtest,
    +-- considering 
    +--  - f is an increasing function
    +--  - f(min)=0
    +--  - f(max)≠0
    +-- then maxZeroIndex f min max nbtest returns x such that
    +--    f(x - ε)=0 and f(x + ε)≠0
    +--    where ε=(max-min)/2^(nbtest+1) 
    +maxZeroIndex :: (Fractional a,Num a,Num b,Eq b) => 
    +                 (a -> b) -> a -> a -> Int -> a
    +maxZeroIndex _ minval maxval 0 = (minval+maxval)/2
    +maxZeroIndex func minval maxval n = 
    +  if func medpoint /= 0 
    +       then maxZeroIndex func minval medpoint (n-1)
    +       else maxZeroIndex func medpoint maxval (n-1)
    +  where medpoint = (minval+maxval)/2
    +
    +ymandel :: Point -> Point -> Point -> Point
    +ymandel x y z = fromIntegral (mandel x y z 64) / 64
    +
    + +
    + +

    And you can also consider minor changes in the YGL.hs source file.

    + +

    Download the source code of this section → 06_Mandelbulb/Mandelbulb.lhs

    +

    Conclusion

    +

    As we can use imperative style in a functional language, know you can use functional style in imperative languages. This article exposed a way to organize some code in a functional way. I’d like to stress the usage of Haskell made it very simple to achieve this.

    +

    Once you are used to pure functional style, it is hard not to see all advantages it offers.

    +

    The code in the two last sections is completely pure and functional. Furthermore I don’t use GLfloat, Color3 or any other OpenGL type. If I want to use another library in the future, I would be able to keep all the pure code and simply update the YGL module.

    +

    The YGL module can be seen as a “wrapper” around 3D display and user interaction. It is a clean separator between the imperative paradigm and functional paradigm.

    +

    If you want to go further, it shouldn’t be hard to add parallelism. This should be easy mainly because most of the visible code is pure. Such an optimization would have been harder by using directly the OpenGL library.

    +

    You should also want to make a more precise object. Because, the Mandelbulb is clearly not convex. But a precise rendering might be very long from O(n².log(n)) to O(n³).

    +
    +
    +
      +
    1. Unfortunately, I couldn’t make this program to work on my Mac. More precisely, I couldn’t make the DevIL library work on Mac to output the image. Yes I have done a brew install libdevil. But even a minimal program who simply write some jpg didn’t worked. I tried both with Haskell and C.

    2. +
    3. Generally in Haskell you need to declare a lot of import lines. This is something I find annoying. In particular, it should be possible to create a special file, Import.hs which make all the necessary import for you, as you generally need them all. I understand why this is cleaner to force the programmer not to do so, but, each time I do a copy/paste, I feel something is wrong. I believe this concern can be generalized to the lack of namespace in Haskell.

    4. +
    5. I tried Complex Double, Complex Float, this current data type with Double and the actual version Float. For rendering a 1024x1024 Mandelbrot set it takes Complex Double about 6.8s, for Complex Float about 5.1s, for the actual version with Double and Float it takes about 1.6 sec. See these sources for testing yourself: https://gist.github.com/2945043. If you really want to things to go faster, use data Complex = C {-# UNPACK #-} !Float {-# UNPACK #-} !Float. It takes only one second instead of 1.6s.

    6. +
    +
    ]]>
    +
    + + Haskell comme un vrai! + + http://yannesposito.com/Scratch/fr/blog/Haskell-the-Hard-Way/index.html + 2012-02-08T00:00:00Z + 2012-02-08T00:00:00Z + Magritte pleasure principle

    + +
    + +

    Je pense vraiment que tous les développeurs devraient apprendre Haskell. Peut-être pas devenir des ninjas d’Haskell, mais au moins savoir ce que ce langage a de particulier. Son apprentissage ouvre énormément l’esprit.

    +

    La plupart des langages partagent les mêmes fondamentaux :

    +
      +
    • les variables
    • +
    • les boucles
    • +
    • les pointeurs1
    • +
    • les structures de données, les objets et les classes
    • +
    +

    Haskell est très différent. Ce langage utilise des concepts dont je n’avais jamais entendu parlé avant. Beaucoup de ces concepts pourront vous aider à devenir un meilleur développeur.

    +

    Plier son esprit à Haskell peut être difficile. Ce le fût pour moi. Dans cet article, j’essaye de fournir les informations qui m’ont manquées lors de mon apprentissage.

    +

    Cet article sera certainement difficile à suivre. Mais c’est voulu. Il n’y a pas de raccourci pour apprendre Haskell. C’est difficile. Mais je pense que c’est une bonne chose. C’est parce qu’Haskell est difficile qu’il est intéressant.

    +

    La manière conventionnelle d’apprendre Haskell est de lire deux livres. En premier “Learn You a Haskell” et ensuite “Real World Haskell”. Je pense aussi que c’est la bonne manière de s’y prendre. Mais apprendre même un tout petit peu d’Haskell est presque impossible sans se plonger réellement dans ces livres.

    +

    Cet article fait un résumé très dense et rapide des aspect majeurs d’Haskell. J’y ai aussi rajouté des informations qui m’ont manqué pendant l’apprentissage de ce langage.

    +

    Pour les francophones ; je suis désolé. Je n’ai pas eu le courage de tout retraduire en français. Sachez cependant que si vous êtes plusieurs à insister, je ferai certainement l’effort de traduire l’article en entier. Et si vous vous sentez d’avoir une bonne âme je ne suis pas contre un peu d’aide. Les sources de cet article sont sur gihub.

    +

    Cet article contient cinq parties :

    +
      +
    • Introduction : un exemple rapide pour montrer qu’Haskell peut être facile.
    • +
    • Les bases d’Haskell : La syntaxe et des notions essentielles
    • +
    • Partie difficile : +
        +
      • Style fonctionnel : un exemple progressif, du style impératif au style fonctionnel ;
      • +
      • Types : la syntaxe et un exemple d’arbre binaire ;
      • +
      • Structure infinie : manipulons un arbre infini !
      • +
    • +
    • Partie de difficulté infernale : +
        +
      • Utiliser les IO : un exemple très minimal ;
      • +
      • Le truc des IO révélé : les détails cachés d’IO qui m’ont manqués
      • +
      • Les monades : incroyable à quel point on peut généraliser
      • +
    • +
    • Appendice : +
        +
      • Revenons sur les arbres infinis : une discussion plus mathématique sur la manipulation d’arbres infinis.
      • +
    • +
    +
    +Note: Chaque fois que vous voyez un séparateur avec un nom de fichier se terminant par lhs, vous pouvez cliquer sur le nom de fichier et télécharger le fichier. Si vous sauvegardez le fichier sour le nom filename.lhs, vous pouvez l’exécuter avec : +
    +runhaskell filename.lhs
    +
    + +

    Certains ne marcheront pas, mais la majorité vous donneront un résultat. Vous devriez voir un lien juste en dessous.

    +
    +
    + +
    +

    01_basic/10_Introduction/00_hello_world.lhs

    +

    +Introduction +

    + +

    +Install +

    + +

    + +

    Tools:

    +
      +
    • ghc: Compiler similar to gcc for C.
    • +
    • ghci: Interactive Haskell (REPL)
    • +
    • runhaskell: Execute a program without compiling it. Convenient but very slow compared to compiled programs.
    • +
    +

    +Don’t be afraid +

    + +

    The Scream

    +

    Many book/articles about Haskell start by introducing some esoteric formula (quick sort, Fibonacci, etc…). I will do the exact opposite. At first I won’t show you any Haskell super power. I will start with similarities between Haskell and other programming languages. Let’s jump to the mandatory “Hello World”.

    +
    +
    main = putStrLn "Hello World!"
    +
    +

    To run it, you can save this code in a hello.hs and:

    +
    ~ runhaskell ./hello.hs
    +Hello World!
    +

    You could also download the literate Haskell source. You should see a link just above the introduction title. Download this file as 00_hello_world.lhs and:

    +
    ~ runhaskell 00_hello_world.lhs
    +Hello World!
    +

    01_basic/10_Introduction/00_hello_world.lhs

    +
    +

    01_basic/10_Introduction/10_hello_you.lhs

    +

    Now, a program asking your name and replying “Hello” using the name you entered:

    +
    +
    main = do
    +    print "What is your name?"
    +    name <- getLine
    +    print ("Hello " ++ name ++ "!")
    +
    +

    First, let us compare with a similar program in some imperative languages:

    +
    # Python
    +print "What is your name?"
    +name = raw_input()
    +print "Hello %s!" % name
    +
    # Ruby
    +puts "What is your name?"
    +name = gets.chomp
    +puts "Hello #{name}!"
    +
    // In C
    +#include <stdio.h>
    +int main (int argc, char **argv) {
    +    char name[666]; // <- An Evil Number!
    +    // What if my name is more than 665 character long?
    +    printf("What is your name?\n"); 
    +    scanf("%s", name);
    +    printf("Hello %s!\n", name);
    +    return 0;
    +}
    +

    The structure is the same, but there are some syntax differences. A major part of this tutorial will be dedicated to explaining why.

    +

    In Haskell, there is a main function and every object has a type. The type of main is IO (). This means, main will cause side effects.

    +

    Just remember that Haskell can look a lot like mainstream imperative languages.

    +

    01_basic/10_Introduction/10_hello_you.lhs

    +
    +

    01_basic/10_Introduction/20_very_basic.lhs

    +

    +Very basic Haskell +

    + +

    Picasso minimal owl

    +

    Before continuing you need to be warned about some essential properties of Haskell.

    +

    Functional

    +

    Haskell is a functional language. If you have an imperative language background, you’ll have to learn a lot of new things. Hopefully many of these new concepts will help you to program even in imperative languages.

    +

    Smart Static Typing

    +

    Instead of being in your way like in C, C++ or Java, the type system is here to help you.

    +

    Purity

    +

    Generally your functions won’t modify anything in the outside world. This means, it can’t modify the value of a variable, can’t get user input, can’t write on the screen, can’t launch a missile. On the other hand, parallelism will be very easy to achieve. Haskell makes it clear where effects occur and where you are pure. Also, it will be far easier to reason about your program. Most bugs will be prevented in the pure parts of your program.

    +

    Furthermore pure functions follow a fundamental law in Haskell:

    +
    +

    Applying a function with the same parameters always returns the same value.

    +
    +

    Laziness

    +

    Laziness by default is a very uncommon language design. By default, Haskell evaluates something only when it is needed. In consequence, it provides a very elegant way to manipulate infinite structures for example.

    +

    A last warning on how you should read Haskell code. For me, it is like reading scientific papers. Some parts are very clear, but when you see a formula, just focus and read slower. Also, while learning Haskell, it really doesn’t matter much if you don’t understand syntax details. If you meet a >>=, <$>, <- or any other weird symbol, just ignore them and follows the flow of the code.

    +

    +Function declaration +

    + +

    You might be used to declare functions like this:

    +

    In C:

    +
    int f(int x, int y) {
    +    return x*x + y*y;
    +}
    +

    In Javascript:

    +
    function f(x,y) {
    +    return x*x + y*y;
    +}
    +

    in Python:

    +
    def f(x,y):
    +    return x*x + y*y
    +

    in Ruby:

    +
    def f(x,y)
    +    x*x + y*y
    +end
    +

    In Scheme:

    +
    (define (f x y)
    +    (+ (* x x) (* y y)))
    +

    Finally, the Haskell way is:

    +
    f x y = x*x + y*y
    +

    Very clean. No parenthesis, no def.

    +

    Don’t forget, Haskell uses functions and types a lot. It is thus very easy to define them. The syntax was particularly well thought for these objects.

    +

    +A Type Example +

    + +

    The usual way is to declare the type of your function. This is not mandatory. The compiler is smart enough to discover it for you.

    +

    Let’s play a little.

    +
    +
    -- We declare the type using ::
    +f :: Int -> Int -> Int
    +f x y = x*x + y*y
    +
    +main = print (f 2 3)
    +
    +
    ~ runhaskell 20_very_basic.lhs
    +13
    +

    01_basic/10_Introduction/20_very_basic.lhs

    +
    +

    01_basic/10_Introduction/21_very_basic.lhs

    +

    Now try

    +
    +
    f :: Int -> Int -> Int
    +f x y = x*x + y*y
    +
    +main = print (f 2.3 4.2)
    +
    +

    You get this error:

    +
    21_very_basic.lhs:6:23:
    +    No instance for (Fractional Int)
    +      arising from the literal `4.2'
    +    Possible fix: add an instance declaration for (Fractional Int)
    +    In the second argument of `f', namely `4.2'
    +    In the first argument of `print', namely `(f 2.3 4.2)'
    +    In the expression: print (f 2.3 4.2)
    +

    The problem: 4.2 isn’t an Int.

    +

    01_basic/10_Introduction/21_very_basic.lhs

    +
    +

    01_basic/10_Introduction/22_very_basic.lhs

    +

    The solution, don’t declare the type for f. Haskell will infer the most general type for us:

    +
    +
    f x y = x*x + y*y
    +
    +main = print (f 2.3 4.2)
    +
    +

    It works! Great, we don’t have to declare a new function for every single type. For example, in C, you’ll have to declare a function for int, for float, for long, for double, etc…

    +

    But, what type should we declare? To discover the type Haskell has found for us, just launch ghci:

    +
    
    +% ghci
    +GHCi, version 7.0.4: http://www.haskell.org/ghc/  :? for help
    +Loading package ghc-prim ... linking ... done.
    +Loading package integer-gmp ... linking ... done.
    +Loading package base ... linking ... done.
    +Loading package ffi-1.0 ... linking ... done.
    +Prelude> let f x y = x*x + y*y
    +Prelude> :type f
    +f :: Num a => a -> a -> a
    +
    + +

    Uh? What is this strange type?

    +
    Num a => a -> a -> a
    +

    First, let’s focus on the right part a -> a -> a. To understand it, just look at a list of progressive examples:

    +

    The written type | Its meaning |
    Int | the type Int |
    Int -> Int | the type function from Int to Int |
    Float -> Int | the type function from Float to Int |
    a -> Int | the type function from any type to Int |
    a -> a | the type function from any type a to the same type a |
    a -> a -> a | the type function of two arguments of any type a to the same type a |

    +

    In the type a -> a -> a, the letter a is a type variable. It means f is a function with two arguments and both arguments and the result have the same type. The type variable a could take many different type value. For example Int, Integer, Float

    +

    So instead of having a forced type like in C with declaring the function for int, long, float, double, etc… We declare only one function like in a dynamically typed language.

    +

    Generally a can be any type. For example a String, an Int, but also more complex types, like Trees, other functions, etc… But here our type is prefixed with Num a =>.

    +

    Num is a type class. A type class can be understood as a set of types. Num contains only types which behave like numbers. More precisely, Num is class containing types who implement a specific list of functions, and in particular (+) and (*).

    +

    Type classes are a very powerful language construct. We can do some incredibly powerful stuff with this. More on this later.

    +

    Finally, Num a => a -> a -> a means:

    +

    Let a be a type belonging to the Num type class. This is a function from type a to (a -> a).

    +

    Yes, strange. In fact, in Haskell no function really has two arguments. Instead all functions have only one argument. But we will note that taking two arguments is equivalent to taking one argument and returning a function taking the second argument as parameter.

    +

    More precisely f 3 4 is equivalent to (f 3) 4. Note f 3 is a function:

    +
    f :: Num a :: a -> a -> a
    +
    +g :: Num a :: a -> a
    +g = f 3
    +
    +g y ⇔ 3*3 + y*y
    +

    Another notation exists for functions. The lambda notation allows us to create functions without assigning them a name. We call them anonymous function. We could have written:

    +
    g = \y -> 3*3 + y*y
    +

    The \ is used because it looks like λ and is ASCII.

    +

    If you are not used to functional programming your brain should start to heat up. It is time to make a real application.

    +

    01_basic/10_Introduction/22_very_basic.lhs

    +
    +

    01_basic/10_Introduction/23_very_basic.lhs

    +

    But just before that, we should verify the type system works as expected:

    +
    +
    f :: Num a => a -> a -> a
    +f x y = x*x + y*y
    +
    +main = print (f 3 2.4)
    +
    +

    It works, because, 3 is a valid representation both for Fractional numbers like Float and for Integer. As 2.4 is a Fractional number, 3 is then interpreted as being also a Fractional number.

    +

    01_basic/10_Introduction/23_very_basic.lhs

    +
    +

    01_basic/10_Introduction/24_very_basic.lhs

    +

    If we force our function to work with different types, it will fail:

    +
    +
    f :: Num a => a -> a -> a
    +f x y = x*x + y*y
    +
    +x :: Int
    +x = 3
    +y :: Float
    +y = 2.4
    +main = print (f x y) -- won't work because type x ≠ type y
    +
    +

    The compiler complains. The two parameters must have the same type.

    +

    If you believe it is a bad idea, and the compiler should make the transformation from a type to another for you, you should really watch this great (and funny) video: WAT

    +

    01_basic/10_Introduction/24_very_basic.lhs

    +

    +Essential Haskell +

    + +

    Kandinsky Gugg

    +

    I suggest you to skim this part. Think of it like a reference. Haskell has a lot of features. Many informations are missing here. Get back here if notation feels strange.

    +

    I use the symbol to state that two expression are equivalent. It is a meta notation, does not exists in Haskell. I will also use to show what is the return of an expression.

    +

    +Notations +

    + +
    +Arithmetic +
    + +
    3 + 2 * 6 / 3 ⇔ 3 + ((2*6)/3)
    +
    +Logic +
    + +
    True || False ⇒ True
    +True && False ⇒ False
    +True == False ⇒ False
    +True /= False ⇒ True  (/=) is the operator for different
    +
    +Powers +
    + +
    x^n     for n an integral (understand Int or Integer)
    +x**y    for y any kind of number (Float for example)
    +

    Integer have no limit except the capacity of your machine:

    +
    4^103
    +102844034832575377634685573909834406561420991602098741459288064
    +

    Yeah! And also rational numbers FTW! But you need to import the module Data.Ratio:

    +
    $ ghci
    +....
    +Prelude> :m Data.Ratio
    +Data.Ratio> (11 % 15) * (5 % 3)
    +11 % 9
    +
    +Lists +
    + +
    []                      ⇔ empty list
    +[1,2,3]                 ⇔ List of integral
    +["foo","bar","baz"]     ⇔ List of String
    +1:[2,3]                 ⇔ [1,2,3], (:) prepend one element
    +1:2:[]                  ⇔ [1,2]
    +[1,2] ++ [3,4]          ⇔ [1,2,3,4], (++) concatenate
    +[1,2,3] ++ ["foo"]      ⇔ ERROR String ≠ Integral
    +[1..4]                  ⇔ [1,2,3,4]
    +[1,3..10]               ⇔ [1,3,5,7,9]
    +[2,3,5,7,11..100]       ⇔ ERROR! I am not so smart!
    +[10,9..1]               ⇔ [10,9,8,7,6,5,4,3,2,1]
    +
    +Strings +
    + +

    In Haskell strings are list of Char.

    +
    'a' :: Char
    +"a" :: [Char]
    +""  ⇔ []
    +"ab" ⇔ ['a','b'] ⇔  'a':"b" ⇔ 'a':['b'] ⇔ 'a':'b':[]
    +"abc" ⇔ "ab"++"c"
    +
    +

    Remark: In real code you shouldn’t use list of char to represent text. You should mostly use Data.Text instead. If you want to represent stream of ASCII char, you should use Data.ByteString.

    +
    +
    +Tuples +
    + +

    The type of couple is (a,b). Elements in a tuple can have different type.

    +
    -- All these tuple are valid
    +(2,"foo")
    +(3,'a',[2,3])
    +((2,"a"),"c",3)
    +
    +fst (x,y)       ⇒  x
    +snd (x,y)       ⇒  y
    +
    +fst (x,y,z)     ⇒  ERROR: fst :: (a,b) -> a
    +snd (x,y,z)     ⇒  ERROR: snd :: (a,b) -> b
    +
    +Deal with parentheses +
    + +

    To remove some parentheses you can use two functions: ($) and (.).

    +
    -- By default:
    +f g h x         ⇔  (((f g) h) x)
    +
    +-- the $ replace parenthesis from the $
    +-- to the end of the expression
    +f g $ h x       ⇔  f g (h x) ⇔ (f g) (h x)
    +f $ g h x       ⇔  f (g h x) ⇔ f ((g h) x)
    +f $ g $ h x     ⇔  f (g (h x))
    +
    +-- (.) the composition function
    +(f . g) x       ⇔  f (g x)
    +(f . g . h) x   ⇔  f (g (h x))
    +
    +

    01_basic/20_Essential_Haskell/10a_Functions.lhs

    +

    +Useful notations for functions +

    + +

    Just a reminder:

    +
    x :: Int            ⇔ x is of type Int
    +x :: a              ⇔ x can be of any type
    +x :: Num a => a     ⇔ x can be any type a
    +                      such that a belongs to Num type class 
    +f :: a -> b         ⇔ f is a function from a to b
    +f :: a -> b -> c    ⇔ f is a function from a to (b→c)
    +f :: (a -> b) -> c  ⇔ f is a function from (a→b) to c
    +

    Defining the type of a function before its declaration isn’t mandatory. Haskell infers the most general type for you. But it is considered a good practice to do so.

    +

    Infix notation

    +
    +
    square :: Num a => a -> a  
    +square x = x^2
    +
    +

    Note ^ use infix notation. For each infix operator there its associated prefix notation. You just have to put it inside parenthesis.

    +
    +
    square' x = (^) x 2
    +
    +square'' x = (^2) x
    +
    +

    We can remove x in the left and right side! It’s called η-reduction.

    +
    +
    square''' = (^2)
    +
    +

    Note we can declare function with ' in their name. Here:

    +
    +

    squaresquare'square''square '''

    +
    +

    Tests

    +

    An implementation of the absolute function.

    +
    +
    absolute :: (Ord a, Num a) => a -> a
    +absolute x = if x >= 0 then x else -x
    +
    +

    Note: the if .. then .. else Haskell notation is more like the ¤?¤:¤ C operator. You cannot forget the else.

    +

    Another equivalent version:

    +
    +
    absolute' x
    +    | x >= 0 = x
    +    | otherwise = -x
    +
    + +
    +

    Notation warning: indentation is important in Haskell. Like in Python, a bad indentation could break your code!

    +
    +
    + +
    +
    main = do
    +      print $ square 10
    +      print $ square' 10
    +      print $ square'' 10
    +      print $ square''' 10
    +      print $ absolute 10
    +      print $ absolute (-10)
    +      print $ absolute' 10
    +      print $ absolute' (-10)
    +
    +
    + +

    01_basic/20_Essential_Haskell/10a_Functions.lhs

    +

    +Hard Part +

    + +

    The hard part can now begin.

    +

    +Functional style +

    + +

    Biomechanical Landscape by H.R. Giger

    +

    In this section, I will give a short example of the impressive refactoring ability provided by Haskell. We will select a problem and solve it using a standard imperative way. Then I will make the code evolve. The end result will be both more elegant and easier to adapt.

    +

    Let’s solve the following problem:

    +
    +

    Given a list of integers, return the sum of the even numbers in the list.

    +

    example: [1,2,3,4,5] ⇒ 2 + 4 ⇒ 6

    +
    +

    To show differences between the functional and imperative approach, I’ll start by providing an imperative solution (in Javascript):

    +
    function evenSum(list) {
    +    var result = 0;
    +    for (var i=0; i< list.length ; i++) {
    +        if (list[i] % 2 ==0) {
    +            result += list[i];
    +        }
    +    }
    +    return result;
    +}
    +

    But, in Haskell we don’t have variables, nor for loop. One solution to achieve the same result without loops is to use recursion.

    +
    +

    Remark: Recursion is generally perceived as slow in imperative languages. But it is generally not the case in functional programming. Most of the time Haskell will handle recursive functions efficiently.

    +
    +

    Here is a C version of the recursive function. Note that for simplicity, I assume the int list ends with the first 0 value.

    +
    int evenSum(int *list) {
    +    return accumSum(0,list);
    +}
    +
    +int accumSum(int n, int *list) {
    +    int x;
    +    int *xs;
    +    if (*list == 0) { // if the list is empty
    +        return n;
    +    } else {
    +        x = list[0]; // let x be the first element of the list
    +        xs = list+1; // let xs be the list without x
    +        if ( 0 == (x%2) ) { // if x is even
    +            return accumSum(n+x, xs);
    +        } else {
    +            return accumSum(n, xs);
    +        }
    +    }
    +}
    +

    Keep this code in mind. We will translate it into Haskell. But before, I need to introduce three simple but useful functions we will use:

    +
    even :: Integral a => a -> Bool
    +head :: [a] -> a
    +tail :: [a] -> [a]
    +

    even verifies if a number is even.

    +
    even :: Integral a => a -> Bool
    +even 3   False
    +even 2   True
    +

    head returns the first element of a list:

    +
    head :: [a] -> a
    +head [1,2,3]  1
    +head []       ERROR
    +

    tail returns all elements of a list, except the first:

    +
    tail :: [a] -> [a]
    +tail [1,2,3]  [2,3]
    +tail [3]      []
    +tail []       ERROR
    +

    Note that for any non empty list l, l ⇔ (head l):(tail l)

    +
    +

    02_Hard_Part/11_Functions.lhs

    +

    The first Haskell solution. The function evenSum returns the sum of all even numbers in a list:

    +
    +
    -- Version 1
    +evenSum :: [Integer] -> Integer
    +
    +evenSum l = accumSum 0 l
    +
    +accumSum n l = if l == []
    +                  then n
    +                  else let x = head l 
    +                           xs = tail l 
    +                       in if even x
    +                              then accumSum (n+x) xs
    +                              else accumSum n xs
    +
    +

    To test a function you can use ghci:

    +
    +% ghci
    +GHCi, version 7.0.3: http://www.haskell.org/ghc/  :? for help
    +Loading package ghc-prim ... linking ... done.
    +Loading package integer-gmp ... linking ... done.
    +Loading package base ... linking ... done.
    +Prelude> :load 11_Functions.lhs 
    +[1 of 1] Compiling Main             ( 11_Functions.lhs, interpreted )
    +Ok, modules loaded: Main.
    +*Main> evenSum [1..5]
    +6
    +
    + +

    Here is an example of execution2:

    +
    +*Main> evenSum [1..5]
    +accumSum 0 [1,2,3,4,5]
    +1 is odd
    +accumSum 0 [2,3,4,5]
    +2 is even
    +accumSum (0+2) [3,4,5]
    +3 is odd
    +accumSum (0+2) [4,5]
    +4 is even
    +accumSum (0+2+4) [5]
    +5 is odd
    +accumSum (0+2+4) []
    +l == []
    +0+2+4
    +0+6
    +6
    +
    + +

    Coming from an imperative language all should seem right. In reality many things can be improved. First, we can generalize the type.

    +
    evenSum :: Integral a => [a] -> a
    +
    + +
    +
    main = do print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/11_Functions.lhs

    +
    +

    02_Hard_Part/12_Functions.lhs

    +

    Next, we can use sub functions using where or let. This way our accumSum function won’t pollute the global namespace.

    +
    +
    -- Version 2
    +evenSum :: Integral a => [a] -> a
    +
    +evenSum l = accumSum 0 l
    +    where accumSum n l = 
    +            if l == []
    +                then n
    +                else let x = head l 
    +                         xs = tail l 
    +                     in if even x
    +                            then accumSum (n+x) xs
    +                            else accumSum n xs
    +
    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/12_Functions.lhs

    +
    +

    02_Hard_Part/13_Functions.lhs

    +

    Next, we can use pattern matching.

    +
    +
    -- Version 3
    +evenSum l = accumSum 0 l
    +    where 
    +        accumSum n [] = n
    +        accumSum n (x:xs) = 
    +             if even x
    +                then accumSum (n+x) xs
    +                else accumSum n xs
    +
    +

    What is pattern matching? Use values instead of general parameter names3.

    +

    Instead of saying: foo l = if l == [] then <x> else <y> You simply state:

    +
    foo [] =  <x>
    +foo l  =  <y>
    +

    But pattern matching goes even further. It is also able to inspect the inner data of a complex value. We can replace

    +
    foo l =  let x  = head l 
    +             xs = tail l
    +         in if even x 
    +             then foo (n+x) xs
    +             else foo n xs
    +

    with

    +
    foo (x:xs) = if even x 
    +                 then foo (n+x) xs
    +                 else foo n xs
    +

    This is a very useful feature. It makes our code both terser and easier to read.

    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/13_Functions.lhs

    +
    +

    02_Hard_Part/14_Functions.lhs

    +

    In Haskell you can simplify function definition by η-reducing them. For example, instead of writing:

    +
    f x = (some expresion) x
    +

    you can simply write

    +
    f = some expression
    +

    We use this method to remove the l:

    +
    +
    -- Version 4
    +evenSum :: Integral a => [a] -> a
    +
    +evenSum = accumSum 0
    +    where 
    +        accumSum n [] = n
    +        accumSum n (x:xs) = 
    +             if even x
    +                then accumSum (n+x) xs
    +                else accumSum n xs
    +
    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/14_Functions.lhs

    +
    +

    02_Hard_Part/15_Functions.lhs

    +

    +Higher Order Functions +

    + +

    Escher

    +

    To make things even better we should use higher order functions. What are these beasts? Higher order functions are functions taking functions as parameter.

    +

    Here are some examples:

    +
    filter :: (a -> Bool) -> [a] -> [a]
    +map :: (a -> b) -> [a] -> [b]
    +foldl :: (a -> b -> a) -> a -> [b] -> a
    +

    Let’s proceed by small steps.

    +
    -- Version 5
    +evenSum l = mysum 0 (filter even l)
    +    where
    +      mysum n [] = n
    +      mysum n (x:xs) = mysum (n+x) xs
    +

    where

    +
    filter even [1..10] ⇔  [2,4,6,8,10]
    +

    The function filter takes a function of type (a -> Bool) and a list of type [a]. It returns a list containing only elements for which the function returned true.

    +

    Our next step is to use another way to simulate a loop. We will use the foldl function to accumulate a value. The function foldl captures a general coding pattern:

    +
    +myfunc list = foo initialValue list
    +    foo accumulated []     = accumulated
    +    foo tmpValue    (x:xs) = foo (bar tmpValue x) xs
    +
    + +

    Which can be replaced by:

    +
    +myfunc list = foldl bar initialValue list
    +
    + +

    If you really want to know how the magic works. Here is the definition of foldl.

    +
    foldl f z [] = z
    +foldl f z (x:xs) = foldl f (f z x) xs
    +
    foldl f z [x1,...xn]
    +⇔  f (... (f (f z x1) x2) ...) xn
    +

    But as Haskell is lazy, it doesn’t evaluate (f z x) and pushes it to the stack. This is why we generally use foldl' instead of foldl; foldl' is a strict version of foldl. If you don’t understand what lazy and strict means, don’t worry, just follow the code as if foldl and foldl' where identical.

    +

    Now our new version of evenSum becomes:

    +
    -- Version 6
    +-- foldl' isn't accessible by default
    +-- we need to import it from the module Data.List
    +import Data.List
    +evenSum l = foldl' mysum 0 (filter even l)
    +  where mysum acc value = acc + value
    +

    Version we can simplify by using directly a lambda notation. This way we don’t have to create the temporary name mysum.

    +
    +
    -- Version 7
    +-- Generally it is considered a good practice
    +-- to import only the necessary function(s)
    +import Data.List (foldl')
    +evenSum l = foldl' (\x y -> x+y) 0 (filter even l)
    +
    +

    And of course, we note that

    +
    (\x y -> x+y) ⇔ (+)
    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/15_Functions.lhs

    +
    +

    02_Hard_Part/16_Functions.lhs

    +

    Finally

    +
    -- Version 8
    +import Data.List (foldl')
    +evenSum :: Integral a => [a] -> a
    +evenSum l = foldl' (+) 0 (filter even l)
    +

    foldl' isn’t the easiest function to intuit. If you are not used to it, you should study it a bit.

    +

    To help you understand what’s going on here, a step by step evaluation:

    +
    +  evenSum [1,2,3,4]
    +⇒ foldl' (+) 0 (filter even [1,2,3,4])
    +⇒ foldl' (+) 0 [2,4]
    +⇒ foldl' (+) (0+2) [4] 
    +⇒ foldl' (+) 2 [4]
    +⇒ foldl' (+) (2+4) []
    +⇒ foldl' (+) 6 []
    +⇒ 6
    +
    + +

    Another useful higher order function is (.). The (.) function corresponds to the mathematical composition.

    +
    (f . g . h) x ⇔  f ( g (h x))
    +

    We can take advantage of this operator to η-reduce our function:

    +
    -- Version 9
    +import Data.List (foldl')
    +evenSum :: Integral a => [a] -> a
    +evenSum = (foldl' (+) 0) . (filter even)
    +

    Also, we could rename some parts to make it clearer:

    +
    +
    -- Version 10 
    +import Data.List (foldl')
    +sum' :: (Num a) => [a] -> a
    +sum' = foldl' (+) 0
    +evenSum :: Integral a => [a] -> a
    +evenSum = sum' . (filter even)
    +
    +

    It is time to discuss a bit. What did we gain by using higher order functions?

    +

    At first, you can say it is terseness. But in fact, it has more to do with better thinking. Suppose we want to modify slightly our function. We want to get the sum of all even square of element of the list.

    +
    [1,2,3,4] ▷ [1,4,9,16] ▷ [4,16] ▷ 20
    +

    Update the version 10 is extremely easy:

    +
    +
    squareEvenSum = sum' . (filter even) . (map (^2))
    +squareEvenSum' = evenSum . (map (^2))
    +squareEvenSum'' = sum' . (map (^2)) . (filter even)
    +
    +

    We just had to add another “transformation function”4.

    +
    map (^2) [1,2,3,4] ⇔ [1,4,9,16]
    +

    The map function simply apply a function to all element of a list.

    +

    We didn’t had to modify anything inside the function definition. It feels more modular. But in addition you can think more mathematically about your function. You can then use your function as any other one. You can compose, map, fold, filter using your new function.

    +

    To modify version 1 is left as an exercise to the reader ☺.

    +

    If you believe we reached the end of generalization, then know you are very wrong. For example, there is a way to not only use this function on lists but on any recursive type. If you want to know how, I suggest you to read this quite fun article: Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire by Meijer, Fokkinga and Paterson.

    +

    This example should show you how great pure functional programming is. Unfortunately, using pure functional programming isn’t well suited to all usages. Or at least such a language hasn’t been found yet.

    +

    One of the great powers of Haskell is the ability to create DSLs (Domain Specific Language) making it easy to change the programming paradigm.

    +

    In fact, Haskell is also great when you want to write imperative style programming. Understanding this was really hard for me when learning Haskell. A lot of effort has been done to explain to you how much functional approach is superior. Then when you start the imperative style of Haskell, it is hard to understand why and how.

    +

    But before talking about this Haskell super-power, we must talk about another essential aspect of Haskell: Types.

    +
    + +
    +
    main = print $ evenSum [1..10]
    +
    +
    + +

    02_Hard_Part/16_Functions.lhs

    +

    +Types +

    + +

    Dali, the madonna of port Lligat

    +
    +

    tl;dr:

    +
      +
    • type Name = AnotherType is just an alias and the compiler doesn’t do any difference between Name and AnotherType.
    • +
    • data Name = NameConstructor AnotherType make a difference.
    • +
    • data can construct structures which can be recursives.
    • +
    • deriving is magic and create functions for you.
    • +
    +
    +

    In Haskell, types are strong and static.

    +

    Why is this important? It will help you greatly to avoid mistakes. In Haskell, most bugs are caught during the compilation of your program. And the main reason is because of the type inference during compilation. It will be easy to detect where you used the wrong parameter at the wrong place for example.

    +

    +Type inference +

    + +

    Static typing is generally essential to reach fast execution time. But most statically typed languages are bad at generalizing concepts. Haskell’s saving grace is that it can infer types.

    +

    Here is a simple example. The square function in Haskell:

    +
    square x = x * x
    +

    This function can square any Numeral type. You can provide square with an Int, an Integer, a Float a Fractional and even Complex. Proof by example:

    +
    % ghci
    +GHCi, version 7.0.4:
    +...
    +Prelude> let square x = x*x
    +Prelude> square 2
    +4
    +Prelude> square 2.1
    +4.41
    +Prelude> -- load the Data.Complex module
    +Prelude> :m Data.Complex
    +Prelude Data.Complex> square (2 :+ 1)
    +3.0 :+ 4.0
    +

    x :+ y is the notation for the complex (x + ib).

    +

    Now compare with the amount of code necessary in C:

    +
    int     int_square(int x) { return x*x; }
    +
    +float   float_square(float x) {return x*x; }
    +
    +complex complex_square (complex z) {
    +    complex tmp;
    +    tmp.real = z.real * z.real - z.img * z.img;
    +    tmp.img = 2 * z.img * z.real;
    +}
    +
    +complex x,y;
    +y = complex_square(x);
    +

    For each type, you need to write a new function. The only way to work around this problem is to use some meta-programming trick. For example using the pre-processor. In C++ there is a better way, the C++ templates:

    +

    ~~~~~~ {.c++} #include #include using namespace std;

    +

    template T square(T x) { return x*x; }

    +

    int main() { // int int sqr_of_five = square(5); cout << sqr_of_five << endl; // double cout << (double)square(5.3) << endl; // complex cout << square( complex(5,3) ) << endl; return 0; } ~~~~~~

    +

    C++ does a far better job than C. For more complex function the syntax can be hard to follow: look at this article for example.

    +

    In C++ you must declare that a function can work with different types. In Haskell this is the opposite. The function will be as general as possible by default.

    +

    Type inference gives Haskell the feeling of freedom that dynamically typed languages provide. But unlike dynamically typed languages, most errors are caught before the execution. Generally, in Haskell:

    +
    +

    “if it compiles it certainly does what you intended”

    +
    +
    +

    02_Hard_Part/21_Types.lhs

    +

    +Type construction +

    + +

    You can construct your own types. First you can use aliases or type synonyms.

    +
    +
    type Name   = String
    +type Color  = String
    +
    +showInfos :: Name ->  Color -> String
    +showInfos name color =  "Name: " ++ name
    +                        ++ ", Color: " ++ color
    +name :: Name
    +name = "Robin"
    +color :: Color
    +color = "Blue"
    +main = putStrLn $ showInfos name color
    +
    +

    02_Hard_Part/21_Types.lhs

    +
    +

    02_Hard_Part/22_Types.lhs

    +

    But it doesn’t protect you much. Try to swap the two parameter of showInfos and run the program:

    +
        putStrLn $ showInfos color name
    +

    It will compile and execute. In fact you can replace Name, Color and String everywhere. The compiler will treat them as completely identical.

    +

    Another method is to create your own types using the keyword data.

    +
    +
    data Name   = NameConstr String
    +data Color  = ColorConstr String
    +
    +showInfos :: Name ->  Color -> String
    +showInfos (NameConstr name) (ColorConstr color) =
    +      "Name: " ++ name ++ ", Color: " ++ color
    +
    +name  = NameConstr "Robin"
    +color = ColorConstr "Blue"
    +main = putStrLn $ showInfos name color
    +
    +

    Now if you switch parameters of showInfos, the compiler complains! A possible mistake you could never do again. The only price is to be more verbose.

    +

    Also remark constructor are functions:

    +
    NameConstr  :: String -> Name
    +ColorConstr :: String -> Color
    +

    The syntax of data is mainly:

    +
    data TypeName =   ConstructorName  [types]
    +                | ConstructorName2 [types]
    +                | ...
    +

    Generally the usage is to use the same name for the DataTypeName and DataTypeConstructor.

    +

    Example:

    +
    data Complex = Num a => Complex a a
    +

    Also you can use the record syntax:

    +
    data DataTypeName = DataConstructor {
    +                      field1 :: [type of field1]
    +                    , field2 :: [type of field2]
    +                    ...
    +                    , fieldn :: [type of fieldn] }
    +

    And many accessors are made for you. Furthermore you can use another order when setting values.

    +

    Example:

    +
    data Complex = Num a => Complex { real :: a, img :: a}
    +c = Complex 1.0 2.0
    +z = Complex { real = 3, img = 4 }
    +real c  1.0
    +img z  4
    +

    02_Hard_Part/22_Types.lhs

    +
    +

    02_Hard_Part/23_Types.lhs

    +

    +Recursive type +

    + +

    You already encountered a recursive type: lists. You can re-create lists, but with a more verbose syntax:

    +
    data List a = Empty | Cons a (List a)
    +

    If you really want to use an easier syntax you can use an infix name for constructors.

    +
    infixr 5 :::
    +data List a = Nil | a ::: (List a)
    +

    The number after infixr is the priority.

    +

    If you want to be able to print (Show), read (Read), test equality (Eq) and compare (Ord) your new data structure you can tell Haskell to derive the appropriate functions for you.

    +
    +
    infixr 5 :::
    +data List a = Nil | a ::: (List a) 
    +              deriving (Show,Read,Eq,Ord)
    +
    +

    When you add deriving (Show) to your data declaration, Haskell create a show function for you. We’ll see soon how you can use your own show function.

    +
    +
    convertList [] = Nil
    +convertList (x:xs) = x ::: convertList xs
    +
    +
    +
    main = do
    +      print (0 ::: 1 ::: Nil)
    +      print (convertList [0,1])
    +
    +

    This prints:

    +
    0 ::: (1 ::: Nil)
    +0 ::: (1 ::: Nil)
    +

    02_Hard_Part/23_Types.lhs

    +
    +

    02_Hard_Part/30_Trees.lhs

    +

    +Trees +

    + +

    Magritte, l

    +

    We’ll just give another standard example: binary trees.

    +
    +
    import Data.List
    +
    +data BinTree a = Empty
    +                 | Node a (BinTree a) (BinTree a)
    +                              deriving (Show)
    +
    +

    We will also create a function which turns a list into an ordered binary tree.

    +
    +
    treeFromList :: (Ord a) => [a] -> BinTree a
    +treeFromList [] = Empty
    +treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
    +                             (treeFromList (filter (>x) xs))
    +
    +

    Look at how elegant this function is. In plain English:

    +
      +
    • an empty list will be converted to an empty tree.
    • +
    • a list (x:xs) will be converted to a tree where:
    • +
    • The root is x
    • +
    • Its left subtree is the tree created from members of the list xs which are strictly inferior to x and
    • +
    • the right subtree is the tree created from members of the list xs which are strictly superior to x.
    • +
    +
    +
    main = print $ treeFromList [7,2,4,8]
    +
    +

    You should obtain the following:

    +
    Node 7 (Node 2 Empty (Node 4 Empty Empty)) (Node 8 Empty Empty)
    +

    This is an informative but quite unpleasant representation of our tree.

    +

    02_Hard_Part/30_Trees.lhs

    +
    +

    02_Hard_Part/31_Trees.lhs

    +

    Just for fun, let’s code a better display for our trees. I simply had fun making a nice function to display trees in a general way. You can safely skip this part if you find it too difficult to follow.

    +

    We have a few changes to make. We remove the deriving (Show) from the declaration of our BinTree type. And it might also be useful to make our BinTree an instance of (Eq and Ord). We will be able to test equality and compare trees.

    +
    +
    data BinTree a = Empty
    +                 | Node a (BinTree a) (BinTree a)
    +                  deriving (Eq,Ord)
    +
    +

    Without the deriving (Show), Haskell doesn’t create a show method for us. We will create our own version of show. To achieve this, we must declare that our newly created type BinTree a is an instance of the type class Show. The general syntax is:

    +
    instance Show (BinTree a) where
    +   show t = ... -- You declare your function here
    +

    Here is my version of how to show a binary tree. Don’t worry about the apparent complexity. I made a lot of improvements in order to display even stranger objects.

    +
    +
    -- declare BinTree a to be an instance of Show
    +instance (Show a) => Show (BinTree a) where
    +  -- will start by a '<' before the root
    +  -- and put a : a begining of line
    +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
    +    where
    +    -- treeshow pref Tree
    +    --   shows a tree and starts each line with pref
    +    -- We don't display the Empty tree
    +    treeshow pref Empty = ""
    +    -- Leaf
    +    treeshow pref (Node x Empty Empty) =
    +                  (pshow pref x)
    +
    +    -- Right branch is empty
    +    treeshow pref (Node x left Empty) =
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " left)
    +
    +    -- Left branch is empty
    +    treeshow pref (Node x Empty right) =
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- Tree with left and right children non empty
    +    treeshow pref (Node x left right) =
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "|--" "|  " left) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- shows a tree using some prefixes to make it nice
    +    showSon pref before next t =
    +                  pref ++ before ++ treeshow (pref ++ next) t
    +
    +    -- pshow replaces "\n" by "\n"++pref
    +    pshow pref x = replace '\n' ("\n"++pref) (show x)
    +
    +    -- replaces one char by another string
    +    replace c new string =
    +      concatMap (change c new) string
    +      where
    +          change c new x
    +              | x == c = new
    +              | otherwise = x:[] -- "x"
    +
    +

    The treeFromList method remains identical.

    +
    +
    treeFromList :: (Ord a) => [a] -> BinTree a
    +treeFromList [] = Empty
    +treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
    +                             (treeFromList (filter (>x) xs))
    +
    +

    And now, we can play:

    +
    +
    main = do
    +  putStrLn "Int binary tree:"
    +  print $ treeFromList [7,2,4,8,1,3,6,21,12,23]
    +
    +
    Int binary tree:
    +< 7
    +: |--2
    +: |  |--1
    +: |  `--4
    +: |     |--3
    +: |     `--6
    +: `--8
    +:    `--21
    +:       |--12
    +:       `--23
    +

    Now it is far better! The root is shown by starting the line with the < character. And each following line starts with a :. But we could also use another type.

    +
    +
      putStrLn "\nString binary tree:"
    +  print $ treeFromList ["foo","bar","baz","gor","yog"]
    +
    +
    String binary tree:
    +< "foo"
    +: |--"bar"
    +: |  `--"baz"
    +: `--"gor"
    +:    `--"yog"
    +

    As we can test equality and order trees, we can make tree of trees!

    +
    +
      putStrLn "\nBinary tree of Char binary trees:"
    +  print ( treeFromList
    +           (map treeFromList ["baz","zara","bar"]))
    +
    +
    Binary tree of Char binary trees:
    +< < 'b'
    +: : |--'a'
    +: : `--'z'
    +: |--< 'b'
    +: |  : |--'a'
    +: |  : `--'r'
    +: `--< 'z'
    +:    : `--'a'
    +:    :    `--'r'
    +

    This is why I chose to prefix each line of tree display by : (except for the root).

    +

    Yo Dawg Tree

    +
    +
      putStrLn "\nTree of Binary trees of Char binary trees:"
    +  print $ (treeFromList . map (treeFromList . map treeFromList))
    +             [ ["YO","DAWG"]
    +             , ["I","HEARD"]
    +             , ["I","HEARD"]
    +             , ["YOU","LIKE","TREES"] ]
    +
    +

    Which is equivalent to

    +
    print ( treeFromList (
    +          map treeFromList
    +             [ map treeFromList ["YO","DAWG"]
    +             , map treeFromList ["I","HEARD"]
    +             , map treeFromList ["I","HEARD"]
    +             , map treeFromList ["YOU","LIKE","TREES"] ]))
    +

    and gives:

    +
    Binary tree of Binary trees of Char binary trees:
    +< < < 'Y'
    +: : : `--'O'
    +: : `--< 'D'
    +: :    : |--'A'
    +: :    : `--'W'
    +: :    :    `--'G'
    +: |--< < 'I'
    +: |  : `--< 'H'
    +: |  :    : |--'E'
    +: |  :    : |  `--'A'
    +: |  :    : |     `--'D'
    +: |  :    : `--'R'
    +: `--< < 'Y'
    +:    : : `--'O'
    +:    : :    `--'U'
    +:    : `--< 'L'
    +:    :    : `--'I'
    +:    :    :    |--'E'
    +:    :    :    `--'K'
    +:    :    `--< 'T'
    +:    :       : `--'R'
    +:    :       :    |--'E'
    +:    :       :    `--'S'
    +

    Notice how duplicate trees aren’t inserted; there is only one tree corresponding to "I","HEARD". We have this for (almost) free, because we have declared Tree to be an instance of Eq.

    +

    See how awesome this structure is. We can make trees containing not only integers, strings and chars, but also other trees. And we can even make a tree containing a tree of trees!

    +

    02_Hard_Part/31_Trees.lhs

    +
    +

    02_Hard_Part/40_Infinites_Structures.lhs

    +

    +Infinite Structures +

    + +

    Escher

    +

    It is often stated that Haskell is lazy.

    +

    In fact, if you are a bit pedantic, you should state that Haskell is non-strict. Laziness is just a common implementation for non-strict languages.

    +

    Then what does not-strict means? From the Haskell wiki:

    +
    +

    Reduction (the mathematical term for evaluation) proceeds from the outside in.

    +

    so if you have (a+(b*c)) then you first reduce + first, then you reduce the inner (b*c)

    +
    +

    For example in Haskell you can do:

    +
    +
    -- numbers = [1,2,..]
    +numbers :: [Integer]
    +numbers = 0:map (1+) numbers
    +
    +take' n [] = []
    +take' 0 l = []
    +take' n (x:xs) = x:take' (n-1) xs
    +
    +main = print $ take' 10 numbers
    +
    +

    And it stops.

    +

    How?

    +

    Instead of trying to evaluate numbers entirely, it evaluates elements only when needed.

    +

    Also, note in Haskell there is a notation for infinite lists

    +
    [1..]   ⇔ [1,2,3,4...]
    +[1,3..] ⇔ [1,3,5,7,9,11...]
    +

    And most functions will work with them. Also, there is a built-in function take which is equivalent to our take'.

    +

    02_Hard_Part/40_Infinites_Structures.lhs

    +
    +

    02_Hard_Part/41_Infinites_Structures.lhs

    +
    + +

    This code is mostly the same as the previous one.

    +
    +
    import Debug.Trace (trace)
    +import Data.List
    +data BinTree a = Empty 
    +                 | Node a (BinTree a) (BinTree a) 
    +                  deriving (Eq,Ord)
    +
    +
    +
    -- declare BinTree a to be an instance of Show
    +instance (Show a) => Show (BinTree a) where
    +  -- will start by a '<' before the root
    +  -- and put a : a begining of line
    +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
    +    where
    +    treeshow pref Empty = ""
    +    treeshow pref (Node x Empty Empty) = 
    +                  (pshow pref x)
    +
    +    treeshow pref (Node x left Empty) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " left)
    +
    +    treeshow pref (Node x Empty right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    treeshow pref (Node x left right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "|--" "|  " left) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- show a tree using some prefixes to make it nice
    +    showSon pref before next t = 
    +                  pref ++ before ++ treeshow (pref ++ next) t
    +
    +    -- pshow replace "\n" by "\n"++pref
    +    pshow pref x = replace '\n' ("\n"++pref) (" " ++ show x)
    +
    +    -- replace on char by another string
    +    replace c new string =
    +      concatMap (change c new) string
    +      where
    +          change c new x 
    +              | x == c = new
    +              | otherwise = x:[] -- "x"
    +
    +
    + +

    Suppose we don’t mind having an ordered binary tree. Here is an infinite binary tree:

    +
    +
    nullTree = Node 0 nullTree nullTree
    +
    +

    A complete binary tree where each node is equal to 0. Now I will prove you can manipulate this object using the following function:

    +
    +
    -- take all element of a BinTree 
    +-- up to some depth
    +treeTakeDepth _ Empty = Empty
    +treeTakeDepth 0 _     = Empty
    +treeTakeDepth n (Node x left right) = let
    +          nl = treeTakeDepth (n-1) left
    +          nr = treeTakeDepth (n-1) right
    +          in
    +              Node x nl nr
    +
    +

    See what occurs for this program:

    +
    main = print $ treeTakeDepth 4 nullTree
    +

    This code compiles, runs and stops giving the following result:

    +
    <  0
    +: |-- 0
    +: |  |-- 0
    +: |  |  |-- 0
    +: |  |  `-- 0
    +: |  `-- 0
    +: |     |-- 0
    +: |     `-- 0
    +: `-- 0
    +:    |-- 0
    +:    |  |-- 0
    +:    |  `-- 0
    +:    `-- 0
    +:       |-- 0
    +:       `-- 0
    +

    Just to heat up your neurones a bit more, let’s make a slightly more interesting tree:

    +
    +
    iTree = Node 0 (dec iTree) (inc iTree)
    +        where
    +           dec (Node x l r) = Node (x-1) (dec l) (dec r) 
    +           inc (Node x l r) = Node (x+1) (inc l) (inc r) 
    +
    +

    Another way to create this tree is to use a higher order function. This function should be similar to map, but should work on BinTree instead of list. Here is such a function:

    +
    +
    -- apply a function to each node of Tree
    +treeMap :: (a -> b) -> BinTree a -> BinTree b
    +treeMap f Empty = Empty
    +treeMap f (Node x left right) = Node (f x) 
    +                                     (treeMap f left) 
    +                                     (treeMap f right)
    +
    +

    Hint: I won’t talk more about this here. If you are interested by the generalization of map to other data structures, search for functor and fmap.

    +

    Our definition is now:

    +
    +
    infTreeTwo :: BinTree Int
    +infTreeTwo = Node 0 (treeMap (\x -> x-1) infTreeTwo) 
    +                    (treeMap (\x -> x+1) infTreeTwo) 
    +
    +

    Look at the result for

    +
    main = print $ treeTakeDepth 4 infTreeTwo
    +
    <  0
    +: |-- -1
    +: |  |-- -2
    +: |  |  |-- -3
    +: |  |  `-- -1
    +: |  `-- 0
    +: |     |-- -1
    +: |     `-- 1
    +: `-- 1
    +:    |-- 0
    +:    |  |-- -1
    +:    |  `-- 1
    +:    `-- 2
    +:       |-- 1
    +:       `-- 3
    +
    + +
    +
    main = do
    +  print $ treeTakeDepth 4 nullTree
    +  print $ treeTakeDepth 4 infTreeTwo
    +
    +
    + +

    02_Hard_Part/41_Infinites_Structures.lhs

    +

    +Hell Difficulty Part +

    + +

    Congratulations for getting so far! Now, some of the really hardcore stuff can start.

    +

    If you are like me, you should get the functional style. You should also understand a bit more the advantages of laziness by default. But you also don’t really understand where to start in order to make a real program. And in particular:

    +
      +
    • How do you deal with effects?
    • +
    • Why is there a strange imperative-like notation for dealing with IO?
    • +
    +

    Be prepared, the answers might be complex. But they all be very rewarding.

    +
    +

    03_Hell/01_IO/01_progressive_io_example.lhs

    +

    +Deal With IO +

    + +

    Magritte, Carte blanche

    +
    +

    tl;dr:

    +

    A typical function doing IO looks a lot like an imperative program:

    +
    f :: IO a
    +f = do
    +  x <- action1
    +  action2 x
    +  y <- action3
    +  action4 x y
    +
      +
    • To set a value to an object we use <- .
    • +
    • The type of each line is IO *; in this example:
    • +
    • action1 :: IO b
    • +
    • action2 x :: IO ()
    • +
    • action3 :: IO c
    • +
    • action4 x y :: IO a
    • +
    • x :: b, y :: c
    • +
    • Few objects have the type IO a, this should help you choose. In particular you cannot use pure functions directly here. To use pure functions you could do action2 (purefunction x) for example.
    • +
    +
    +

    In this section, I will explain how to use IO, not how it works. You’ll see how Haskell separates the pure from the impure parts of the program.

    +

    Don’t stop because you’re trying to understand the details of the syntax. Answers will come in the next section.

    +

    What to achieve?

    +
    +

    Ask a user to enter a list of numbers. Print the sum of the numbers

    +
    +
    +
    toList :: String -> [Integer]
    +toList input = read ("[" ++ input ++ "]")
    +
    +main = do
    +  putStrLn "Enter a list of numbers (separated by comma):"
    +  input <- getLine
    +  print $ sum (toList input)
    +
    +

    It should be straightforward to understand the behavior of this program. Let’s analyze the types in more detail.

    +
    putStrLn :: String -> IO ()
    +getLine  :: IO String
    +print    :: Show a => a -> IO ()
    +

    Or more interestingly, we note that each expression in the do block has a type of IO a.

    +
    +main = do
    +  putStrLn "Enter ... " :: IO ()
    +  getLine               :: IO String
    +  print Something       :: IO ()
    +
    + +

    We should also pay attention to the effect of the <- symbol.

    +
    do
    + x <- something
    +

    If something :: IO a then x :: a.

    +

    Another important note about using IO. All lines in a do block must be of one of the two forms:

    +
    action1             :: IO a
    +                    -- in this case, generally a = ()
    +

    or

    +
    value <- action2    -- where
    +                    -- bar z t :: IO b
    +                    -- value   :: b
    +

    These two kinds of line will correspond to two different ways of sequencing actions. The meaning of this sentence should be clearer by the end of the next section.

    +

    03_Hell/01_IO/01_progressive_io_example.lhs

    +
    +

    03_Hell/01_IO/02_progressive_io_example.lhs

    +

    Now let’s see how this program behaves. For example, what occur if the user enter something strange? Let’s try:

    +
        % runghc 02_progressive_io_example.lhs
    +    Enter a list of numbers (separated by comma):
    +    foo
    +    Prelude.read: no parse
    +

    Argh! An evil error message and a crash! The first evolution will be to answer with a more friendly message.

    +

    In order to do this, we must detect that something went wrong. Here is one way to do this. Use the type Maybe. It is a very common type in Haskell.

    +
    +
    import Data.Maybe
    +
    +

    What is this thing? Maybe is a type which takes one parameter. Its definition is:

    +
    data Maybe a = Nothing | Just a
    +

    This is a nice way to tell there was an error while trying to create/compute a value. The maybeRead function is a great example of this. This is a function similar to the function read5, but if something goes wrong the returned value is Nothing. If the value is right, it returns Just <the value>. Don’t try to understand too much of this function. I use a lower level function than read; reads.

    +
    +
    maybeRead :: Read a => String -> Maybe a
    +maybeRead s = case reads s of
    +                  [(x,"")]    -> Just x
    +                  _           -> Nothing
    +
    +

    Now to be a bit more readable, we define a function which goes like this: If the string has the wrong format, it will return Nothing. Otherwise, for example for “1,2,3”, it will return Just [1,2,3].

    +
    +
    getListFromString :: String -> Maybe [Integer]
    +getListFromString str = maybeRead $ "[" ++ str ++ "]"
    +
    +

    We simply have to test the value in our main function.

    +
    +
    main :: IO ()
    +main = do
    +  putStrLn "Enter a list of numbers (separated by comma):"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> print (sum l)
    +          Nothing -> error "Bad format. Good Bye."
    +
    +

    In case of error, we display a nice error message.

    +

    Note that the type of each expression in the main’s do block remains of the form IO a. The only strange construction is error. I’ll say error msg will simply take the needed type (here IO ()).

    +

    One very important thing to note is the type of all the functions defined so far. There is only one function which contains IO in its type: main. This means main is impure. But main uses getListFromString which is pure. It is then clear just by looking at declared types which functions are pure and which are impure.

    +

    Why does purity matter? I certainly forget many advantages, but the three main reasons are:

    +
      +
    • It is far easier to think about pure code than impure one.
    • +
    • Purity protects you from all the hard to reproduce bugs due to side effects.
    • +
    • You can evaluate pure functions in any order or in parallel without risk.
    • +
    +

    This is why you should generally put as most code as possible inside pure functions.

    +

    03_Hell/01_IO/02_progressive_io_example.lhs

    +
    +

    03_Hell/01_IO/03_progressive_io_example.lhs

    +

    Our next evolution will be to prompt the user again and again until she enters a valid answer.

    +

    We keep the first part:

    +
    +
    import Data.Maybe
    +
    +maybeRead :: Read a => String -> Maybe a
    +maybeRead s = case reads s of
    +                  [(x,"")]    -> Just x
    +                  _           -> Nothing
    +getListFromString :: String -> Maybe [Integer]
    +getListFromString str = maybeRead $ "[" ++ str ++ "]"
    +
    +

    Now, we create a function which will ask the user for an list of integers until the input is right.

    +
    +
    askUser :: IO [Integer]
    +askUser = do
    +  putStrLn "Enter a list of numbers (separated by comma):"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> return l
    +          Nothing -> askUser
    +
    +

    This function is of type IO [Integer]. Such a type means that we retrieved a value of type [Integer] through some IO actions. Some people might explain while waving their hands:

    +
    +

    «This is an [Integer] inside an IO»

    +
    +

    If you want to understand the details behind all of this, you’ll have to read the next section. But sincerely, if you just want to use IO. Just practice a little and remember to think about the type.

    +

    Finally our main function is quite simpler:

    +
    +
    main :: IO ()
    +main = do
    +  list <- askUser
    +  print $ sum list
    +
    +

    We have finished with our introduction to IO. This was quite fast. Here are the main things to remember:

    +
      +
    • in the do bloc, each expression must have the type IO a. You are then limited in the number of expressions available. For example, getLine, print, putStrLn, etc…
    • +
    • Try to externalize the pure functions as much as possible.
    • +
    • the IO a type means: an IO action which returns an element of type a. IO represents actions; under the hood, IO a is the type of a function. Read the next section if you are curious.
    • +
    +

    If you practice a bit, you should be able to use IO.

    +
    +

    Exercises:

    +
      +
    • Make a program that sums all of its arguments. Hint: use the function getArgs.
    • +
    +
    +

    03_Hell/01_IO/03_progressive_io_example.lhs

    +

    +IO trick explained +

    + +

    Magritte, ceci n

    +
    +

    Here is a tl;dr: for this section.

    +

    To separate pure and impure parts, main is defined as a function which modifies the state of the world

    +
    main :: World -> World
    +

    A function is guaranteed to have side effects only if it has this type. But look at a typical main function:

    +
    main w0 =
    +    let (v1,w1) = action1 w0 in
    +    let (v2,w2) = action2 v1 w1 in
    +    let (v3,w3) = action3 v2 w2 in
    +    action4 v3 w3
    +

    We have a lot of temporary elements (here w1, w2 and w3) which must be passed on to the next action.

    +

    We create a function bind or (>>=). With bind we don’t need temporary names anymore.

    +
    main =
    +  action1 >>= action2 >>= action3 >>= action4
    +

    Bonus: Haskell has syntactical sugar for us:

    +
    main = do
    +  v1 <- action1
    +  v2 <- action2 v1
    +  v3 <- action3 v2
    +  action4 v3
    +
    +

    Why did we use this strange syntax, and what exactly is this IO type? It looks a bit like magic.

    +

    For now let’s just forget all about the pure parts of our program, and focus on the impure parts:

    +
    askUser :: IO [Integer]
    +askUser = do
    +  putStrLn "Enter a list of numbers (separated by commas):"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> return l
    +          Nothing -> askUser
    +
    +main :: IO ()
    +main = do
    +  list <- askUser
    +  print $ sum list
    +

    First remark; it looks like an imperative structure. Haskell is powerful enough to make impure code look imperative. For example, if you wish you could create a while in Haskell. In fact, for dealing with IO, imperative style is generally more appropriate.

    +

    But you should had noticed the notation is a bit unusual. Here is why, in detail.

    +

    In an impure language, the state of the world can be seen as a huge hidden global variable. This hidden variable is accessible by all functions of your language. For example, you can read and write a file in any function. The fact that a file exists or not can be seen as different states of the world.

    +

    For Haskell this state is not hidden. It is explicitly said main is a function that potentially changes the state of the world. Its type is then something like:

    +
    main :: World -> World
    +

    Not all functions may have access to this variable. Those which have access to this variable are impure. Functions to which the world variable isn’t provided are pure6.

    +

    Haskell considers the state of the world as an input variable to main. But the real type of main is closer to this one7:

    +
    main :: World -> ((),World)
    +

    The () type is the null type. Nothing to see here.

    +

    Now let’s rewrite our main function with this in mind:

    +
    main w0 =
    +    let (list,w1) = askUser w0 in
    +    let (x,w2) = print (sum list,w1) in
    +    x
    +

    First, we note that all functions which have side effects must have the type:

    +
    World -> (a,World)
    +

    Where a is the type of the result. For example, a getChar function should have the type World -> (Char,World).

    +

    Another thing to note is the trick to fix the order of evaluation. In Haskell, in order to evaluate f a b, you have many choices:

    +
      +
    • first eval a then b then f a b
    • +
    • first eval b then a then f a b.
    • +
    • eval a and b in parallel then f a b
    • +
    +

    This is true, because we should work in a pure language.

    +

    Now, if you look at the main function, it is clear you must eval the first line before the second one since, to evaluate the second line you have to get a parameter given by the evaluation of the first line.

    +

    Such trick works nicely. The compiler will at each step provide a pointer to a new real world id. Under the hood, print will evaluate as:

    +
      +
    • print something on the screen
    • +
    • modify the id of the world
    • +
    • evaluate as ((),new world id).
    • +
    +

    Now, if you look at the style of the main function, it is clearly awkward. Let’s try to do the same to the askUser function:

    +
    askUser :: World -> ([Integer],World)
    +

    Before:

    +
    askUser :: IO [Integer]
    +askUser = do
    +  putStrLn "Enter a list of numbers:"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> return l
    +          Nothing -> askUser
    +

    After:

    +
    askUser w0 =
    +    let (_,w1)     = putStrLn "Enter a list of numbers:" in
    +    let (input,w2) = getLine w1 in
    +    let (l,w3)     = case getListFromString input of
    +                      Just l   -> (l,w2)
    +                      Nothing  -> askUser w2
    +    in
    +        (l,w3)
    +

    This is similar, but awkward. Look at all these temporary w? names.

    +

    The lesson, is, naive IO implementation in Pure functional languages is awkward!

    +

    Fortunately, there is a better way to handle this problem. We see a pattern. Each line is of the form:

    +
    let (y,w') = action x w in
    +

    Even if for some line the first x argument isn’t needed. The output type is a couple, (answer, newWorldValue). Each function f must have a type similar to:

    +
    f :: World -> (a,World)
    +

    Not only this, but we can also note that we always follow the same usage pattern:

    +
    let (y,w1) = action1 w0 in
    +let (z,w2) = action2 w1 in
    +let (t,w3) = action3 w2 in
    +...
    +

    Each action can take from 0 to n parameters. And in particular, each action can take a parameter from the result of a line above.

    +

    For example, we could also have:

    +
    let (_,w1) = action1 x w0   in
    +let (z,w2) = action2 w1     in
    +let (_,w3) = action3 x z w2 in
    +...
    +

    And of course actionN w :: (World) -> (a,World).

    +
    +

    IMPORTANT, there are only two important patterns to consider:

    +
    let (x,w1) = action1 w0 in
    +let (y,w2) = action2 x w1 in
    +

    and

    +
    let (_,w1) = action1 w0 in
    +let (y,w2) = action2 w1 in
    +
    +

    Jocker pencil trick

    +

    Now, we will do a magic trick. We will make the temporary world symbol “disappear”. We will bind the two lines. Let’s define the bind function. Its type is quite intimidating at first:

    +
    bind :: (World -> (a,World))
    +        -> (a -> (World -> (b,World)))
    +        -> (World -> (b,World))
    +

    But remember that (World -> (a,World)) is the type for an IO action. Now let’s rename it for clarity:

    +
    type IO a = World -> (a, World)
    +

    Some example of functions:

    +
    getLine :: IO String
    +print :: Show a => a -> IO ()
    +

    getLine is an IO action which takes a world as parameter and returns a couple (String,World). Which can be summarized as: getLine is of type IO String. Which we also see as, an IO action which will return a String “embeded inside an IO”.

    +

    The function print is also interesting. It takes one argument which can be shown. In fact it takes two arguments. The first is the value to print and the other is the state of world. It then returns a couple of type ((),World). This means it changes the state of the world, but doesn’t yield anymore data.

    +

    This type helps us simplify the type of bind:

    +
    bind :: IO a
    +        -> (a -> IO b)
    +        -> IO b
    +

    It says that bind takes two IO actions as parameter and return another IO action.

    +

    Now, remember the important patterns. The first was:

    +
    let (x,w1) = action1 w0 in
    +let (y,w2) = action2 x w1 in
    +(y,w2)
    +

    Look at the types:

    +
    action1  :: IO a
    +action2  :: a -> IO b
    +(y,w2)   :: IO b
    +

    Doesn’t it seem familiar?

    +
    (bind action1 action2) w0 =
    +    let (x, w1) = action1 w0
    +        (y, w2) = action2 x w1
    +    in  (y, w2)
    +

    The idea is to hide the World argument with this function. Let’s go: As an example imagine if we wanted to simulate:

    +
    let (line1,w1) = getLine w0 in
    +let ((),w2) = print line1 in
    +((),w2)
    +

    Now, using the bind function:

    +
    (res,w2) = (bind getLine (\l -> print l)) w0
    +

    As print is of type (World -> ((),World)), we know res = () (null type). If you didn’t see what was magic here, let’s try with three lines this time.

    +
    let (line1,w1) = getLine w0 in
    +let (line2,w2) = getLine w1 in
    +let ((),w3) = print (line1 ++ line2) in
    +((),w3)
    +

    Which is equivalent to:

    +
    (res,w3) = bind getLine (\line1 ->
    +             bind getLine (\line2 ->
    +               print (line1 ++ line2)))
    +

    Didn’t you notice something? Yes, no temporary World variables are used anywhere! This is MA. GIC.

    +

    We can use a better notation. Let’s use (>>=) instead of bind. (>>=) is an infix function like (+); reminder 3 + 4 ⇔ (+) 3 4

    +
    (res,w3) = getLine >>=
    +           \line1 -> getLine >>=
    +           \line2 -> print (line1 ++ line2)
    +

    Ho Ho Ho! Happy Christmas Everyone! Haskell has made syntactical sugar for us:

    +
    do
    +  x <- action1
    +  y <- action2
    +  z <- action3
    +  ...
    +

    Is replaced by:

    +
    action1 >>= \x ->
    +action2 >>= \y ->
    +action3 >>= \z ->
    +...
    +

    Note you can use x in action2 and x and y in action3.

    +

    But what about the lines not using the <-? Easy, another function blindBind:

    +
    blindBind :: IO a -> IO b -> IO b
    +blindBind action1 action2 w0 =
    +    bind action (\_ -> action2) w0
    +

    I didn’t simplify this definition for clarity purpose. Of course we can use a better notation, we’ll use the (>>) operator.

    +

    And

    +
    do
    +    action1
    +    action2
    +    action3
    +

    Is transformed into

    +
    action1 >>
    +action2 >>
    +action3
    +

    Also, another function is quite useful.

    +
    putInIO :: a -> IO a
    +putInIO x = IO (\w -> (x,w))
    +

    This is the general way to put pure values inside the “IO context”. The general name for putInIO is return. This is quite a bad name when you learn Haskell. return is very different from what you might be used to.

    +
    +

    03_Hell/01_IO/21_Detailled_IO.lhs

    +

    To finish, let’s translate our example:

    +
    
    +askUser :: IO [Integer]
    +askUser = do
    +  putStrLn "Enter a list of numbers (separated by commas):"
    +  input <- getLine
    +  let maybeList = getListFromString input in
    +      case maybeList of
    +          Just l  -> return l
    +          Nothing -> askUser
    +
    +main :: IO ()
    +main = do
    +  list <- askUser
    +  print $ sum list
    +

    Is translated into:

    +
    +
    import Data.Maybe
    +
    +maybeRead :: Read a => String -> Maybe a
    +maybeRead s = case reads s of
    +                  [(x,"")]    -> Just x
    +                  _           -> Nothing
    +getListFromString :: String -> Maybe [Integer]
    +getListFromString str = maybeRead $ "[" ++ str ++ "]"
    +askUser :: IO [Integer]
    +askUser = 
    +    putStrLn "Enter a list of numbers (sep. by commas):" >>
    +    getLine >>= \input ->
    +    let maybeList = getListFromString input in
    +      case maybeList of
    +        Just l -> return l
    +        Nothing -> askUser
    +
    +main :: IO ()
    +main = askUser >>=
    +  \list -> print $ sum list
    +
    +

    You can compile this code to verify it keeps working.

    +

    Imagine what it would look like without the (>>) and (>>=).

    +

    03_Hell/01_IO/21_Detailled_IO.lhs

    +
    +

    03_Hell/02_Monads/10_Monads.lhs

    +

    +Monads +

    + +

    Dali, reve. It represents a weapon out of the mouth of a tiger, itself out of the mouth of another tiger, itself out of the mouth of a fish itself out of a grenade. I could have choosen a picture of the Human centipede as it is a very good representation of what a monad really is. But just to thing about it, I find this disgusting and that wasn

    +

    Now the secret can be revealed: IO is a monad. Being a monad means you have access to some syntactical sugar with the do notation. But mainly, you have access to a coding pattern which will ease the flow of your code.

    +
    +

    Important remarks:

    +
      +
    • Monad are not necessarily about effects! There are a lot of pure monads.
    • +
    • Monad are more about sequencing
    • +
    +
    +

    For the Haskell language Monad is a type class. To be an instance of this type class, you must provide the functions (>>=) and return. The function (>>) will be derived from (>>=). Here is how the type class Monad is declared (mostly):

    +
    class Monad m  where
    +  (>>=) :: m a -> (a -> m b) -> m b
    +  return :: a -> m a
    +
    +  (>>) :: m a -> m b -> m b
    +  f >> g = f >>= \_ -> g
    +
    +  -- You should generally safely ignore this function
    +  -- which I believe exists for historical reason
    +  fail :: String -> m a
    +  fail = error
    +
    +

    Remarks:

    +
      +
    • the keyword class is not your friend. A Haskell class is not a class like in object model. A Haskell class has a lot of similarities with Java interfaces. A better word should have been typeclass. That means a set of types. For a type to belong to a class, all functions of the class must be provided for this type.
    • +
    • In this particular example of type class, the type m must be a type that takes an argument. for example IO a, but also Maybe a, [a], etc…
    • +
    • To be a useful monad, your function must obey some rules. If your construction does not obey these rules strange things might happens:
    • +
    +

    ~ return a >>= k == k a m >>= return == m m >>= (-> k x >>= h) == (m >>= k) >>= h ~

    +
    +

    +Maybe is a monad +

    + +

    There are a lot of different types that are instance of Monad. One of the easiest to describe is Maybe. If you have a sequence of Maybe values, you can use monads to manipulate them. It is particularly useful to remove very deep if..then..else.. constructions.

    +

    Imagine a complex bank operation. You are eligible to gain about 700€ only if you can afford to follow a list of operations without being negative.

    +
    +
    deposit  value account = account + value
    +withdraw value account = account - value
    +
    +eligible :: (Num a,Ord a) => a -> Bool
    +eligible account =
    +  let account1 = deposit 100 account in
    +    if (account1 < 0)
    +    then False
    +    else
    +      let account2 = withdraw 200 account1 in
    +      if (account2 < 0)
    +      then False
    +      else
    +        let account3 = deposit 100 account2 in
    +        if (account3 < 0)
    +        then False
    +        else
    +          let account4 = withdraw 300 account3 in
    +          if (account4 < 0)
    +          then False
    +          else
    +            let account5 = deposit 1000 account4 in
    +            if (account5 < 0)
    +            then False
    +            else
    +              True
    +
    +main = do
    +  print $ eligible 300 -- True
    +  print $ eligible 299 -- False
    +
    +

    03_Hell/02_Monads/10_Monads.lhs

    +
    +

    03_Hell/02_Monads/11_Monads.lhs

    +

    Now, let’s make it better using Maybe and the fact that it is a Monad

    +
    +
    deposit :: (Num a) => a -> a -> Maybe a
    +deposit value account = Just (account + value)
    +
    +withdraw :: (Num a,Ord a) => a -> a -> Maybe a
    +withdraw value account = if (account < value) 
    +                         then Nothing 
    +                         else Just (account - value)
    +
    +eligible :: (Num a, Ord a) => a -> Maybe Bool
    +eligible account = do
    +  account1 <- deposit 100 account 
    +  account2 <- withdraw 200 account1 
    +  account3 <- deposit 100 account2 
    +  account4 <- withdraw 300 account3 
    +  account5 <- deposit 1000 account4
    +  Just True
    +
    +main = do
    +  print $ eligible 300 -- Just True
    +  print $ eligible 299 -- Nothing
    +
    +

    03_Hell/02_Monads/11_Monads.lhs

    +
    +

    03_Hell/02_Monads/12_Monads.lhs

    +

    Not bad, but we can make it even better:

    +
    +
    deposit :: (Num a) => a -> a -> Maybe a
    +deposit value account = Just (account + value)
    +
    +withdraw :: (Num a,Ord a) => a -> a -> Maybe a
    +withdraw value account = if (account < value) 
    +                         then Nothing 
    +                         else Just (account - value)
    +
    +eligible :: (Num a, Ord a) => a -> Maybe Bool
    +eligible account =
    +  deposit 100 account >>=
    +  withdraw 200 >>=
    +  deposit 100  >>=
    +  withdraw 300 >>=
    +  deposit 1000 >>
    +  return True
    +
    +main = do
    +  print $ eligible 300 -- Just True
    +  print $ eligible 299 -- Nothing
    +
    +

    We have proven that Monads are a good way to make our code more elegant. Note this idea of code organization, in particular for Maybe can be used in most imperative language. In fact, this is the kind of construction we make naturally.

    +
    +

    An important remark:

    +

    The first element in the sequence being evaluated to Nothing will stop the complete evaluation. This means you don’t execute all lines. You have this for free, thanks to laziness.

    +
    +

    You could also replay these example with the definition of (>>=) for Maybe in mind:

    +
    instance Monad Maybe where
    +    (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
    +    Nothing  >>= _  = Nothing
    +    (Just x) >>= f  = f x
    +
    +    return x = Just x
    +

    The Maybe monad proved to be useful while being a very simple example. We saw the utility of the IO monad. But now a cooler example, lists.

    +

    03_Hell/02_Monads/12_Monads.lhs

    +
    +

    03_Hell/02_Monads/13_Monads.lhs

    +

    +The list monad +

    + +

    Golconde de Magritte

    +

    The list monad helps us to simulate non deterministic computations. Here we go:

    +
    +
    import Control.Monad (guard)
    +
    +allCases = [1..10]
    +
    +resolve :: [(Int,Int,Int)]
    +resolve = do
    +              x <- allCases
    +              y <- allCases
    +              z <- allCases
    +              guard $ 4*x + 2*y < z
    +              return (x,y,z)
    +
    +main = do
    +  print resolve
    +
    +

    MA. GIC. :

    +
    [(1,1,7),(1,1,8),(1,1,9),(1,1,10),(1,2,9),(1,2,10)]
    +

    For the list monad, there is also a syntactical sugar:

    +
    +
      print $ [ (x,y,z) | x <- allCases,
    +                      y <- allCases,
    +                      z <- allCases,
    +                      4*x + 2*y < z ]
    +
    +

    I won’t list all the monads, but there are many monads. Using monads simplifies the manipulation of several notions in pure languages. In particular, monad are very useful for:

    +
      +
    • IO,
    • +
    • non deterministic computation,
    • +
    • generating pseudo random numbers,
    • +
    • keeping configuration state,
    • +
    • writing state,
    • +
    • +
    +

    If you have followed me until here, then you’ve done it! You know monads8!

    +

    03_Hell/02_Monads/13_Monads.lhs

    +

    +Appendix +

    + +

    This section is not so much about learning Haskell. It is just here to discuss some details further.

    +
    +

    04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs

    +

    +More on Infinite Tree +

    + +

    In the section Infinite Structures we saw some simple constructions. Unfortunately we removed two properties from our tree:

    +
      +
    1. no duplicate node value
    2. +
    3. well ordered tree
    4. +
    +

    In this section we will try to keep the first property. Concerning the second one, we must relax it but we’ll discuss how to keep it as much as possible.

    +
    + +

    This code is mostly the same as the one in the tree section.

    +
    +
    import Data.List
    +data BinTree a = Empty 
    +                 | Node a (BinTree a) (BinTree a) 
    +                  deriving (Eq,Ord)
    +
    +-- declare BinTree a to be an instance of Show
    +instance (Show a) => Show (BinTree a) where
    +  -- will start by a '<' before the root
    +  -- and put a : a begining of line
    +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
    +    where
    +    treeshow pref Empty = ""
    +    treeshow pref (Node x Empty Empty) = 
    +                  (pshow pref x)
    +
    +    treeshow pref (Node x left Empty) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " left)
    +
    +    treeshow pref (Node x Empty right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    treeshow pref (Node x left right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "|--" "|  " left) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- show a tree using some prefixes to make it nice
    +    showSon pref before next t = 
    +                  pref ++ before ++ treeshow (pref ++ next) t
    +
    +    -- pshow replace "\n" by "\n"++pref
    +    pshow pref x = replace '\n' ("\n"++pref) (show x)
    +
    +    -- replace on char by another string
    +    replace c new string =
    +      concatMap (change c new) string
    +      where
    +          change c new x 
    +              | x == c = new
    +              | otherwise = x:[] -- "x"
    +
    +
    + +

    Our first step is to create some pseudo-random number list:

    +
    +
    shuffle = map (\x -> (x*3123) `mod` 4331) [1..]
    +
    +

    Just as a reminder, here is the definition of treeFromList

    +
    +
    treeFromList :: (Ord a) => [a] -> BinTree a
    +treeFromList []    = Empty
    +treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
    +                             (treeFromList (filter (>x) xs))
    +
    +

    and treeTakeDepth:

    +
    +
    treeTakeDepth _ Empty = Empty
    +treeTakeDepth 0 _     = Empty
    +treeTakeDepth n (Node x left right) = let
    +          nl = treeTakeDepth (n-1) left
    +          nr = treeTakeDepth (n-1) right
    +          in
    +              Node x nl nr
    +
    +

    See the result of:

    +
    +
    main = do
    +      putStrLn "take 10 shuffle"
    +      print $ take 10 shuffle
    +      putStrLn "\ntreeTakeDepth 4 (treeFromList shuffle)"
    +      print $ treeTakeDepth 4 (treeFromList shuffle)
    +
    +
    % runghc 02_Hard_Part/41_Infinites_Structures.lhs
    +take 10 shuffle
    +[3123,1915,707,3830,2622,1414,206,3329,2121,913]
    +treeTakeDepth 4 (treeFromList shuffle)
    +
    +< 3123
    +: |--1915
    +: |  |--707
    +: |  |  |--206
    +: |  |  `--1414
    +: |  `--2622
    +: |     |--2121
    +: |     `--2828
    +: `--3830
    +:    |--3329
    +:    |  |--3240
    +:    |  `--3535
    +:    `--4036
    +:       |--3947
    +:       `--4242
    +

    Yay! It ends! Beware though, it will only work if you always have something to put into a branch.

    +

    For example

    +
    treeTakeDepth 4 (treeFromList [1..]) 
    +

    will loop forever. Simply because it will try to access the head of filter (<1) [2..]. But filter is not smart enought to understand that the result is the empty list.

    +

    Nonetheless, it is still a very cool example of what non strict programs have to offer.

    +

    Left as an exercise to the reader:

    +
      +
    • Prove the existence of a number n so that treeTakeDepth n (treeFromList shuffle) will enter an infinite loop.
    • +
    • Find an upper bound for n.
    • +
    • Prove there is no shuffle list so that, for any depth, the program ends.
    • +
    +

    04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs

    +
    +

    04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs

    +
    + +

    This code is mostly the same as the preceding one.

    +
    +
    import Debug.Trace (trace)
    +import Data.List
    +data BinTree a = Empty 
    +                 | Node a (BinTree a) (BinTree a) 
    +                  deriving (Eq,Ord)
    +
    +
    +
    -- declare BinTree a to be an instance of Show
    +instance (Show a) => Show (BinTree a) where
    +  -- will start by a '<' before the root
    +  -- and put a : a begining of line
    +  show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
    +    where
    +    treeshow pref Empty = ""
    +    treeshow pref (Node x Empty Empty) = 
    +                  (pshow pref x)
    +
    +    treeshow pref (Node x left Empty) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " left)
    +
    +    treeshow pref (Node x Empty right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    treeshow pref (Node x left right) = 
    +                  (pshow pref x) ++ "\n" ++
    +                  (showSon pref "|--" "|  " left) ++ "\n" ++
    +                  (showSon pref "`--" "   " right)
    +
    +    -- show a tree using some prefixes to make it nice
    +    showSon pref before next t = 
    +                  pref ++ before ++ treeshow (pref ++ next) t
    +
    +    -- pshow replace "\n" by "\n"++pref
    +    pshow pref x = replace '\n' ("\n"++pref) (" " ++ show x)
    +
    +    -- replace on char by another string
    +    replace c new string =
    +      concatMap (change c new) string
    +      where
    +          change c new x 
    +              | x == c = new
    +              | otherwise = x:[] -- "x"
    +
    +treeTakeDepth _ Empty = Empty
    +treeTakeDepth 0 _     = Empty
    +treeTakeDepth n (Node x left right) = let
    +          nl = treeTakeDepth (n-1) left
    +          nr = treeTakeDepth (n-1) right
    +          in
    +              Node x nl nr
    +
    +
    + +

    In order to resolve these problem we will modify slightly our treeFromList and shuffle function.

    +

    A first problem, is the lack of infinite different number in our implementation of shuffle. We generated only 4331 different numbers. To resolve this we make a slightly better shuffle function.

    +
    +
    shuffle = map rand [1..]
    +          where 
    +              rand x = ((p x) `mod` (x+c)) - ((x+c) `div` 2)
    +              p x = m*x^2 + n*x + o -- some polynome
    +              m = 3123    
    +              n = 31
    +              o = 7641
    +              c = 1237
    +
    +

    This shuffle function has the property (hopefully) not to have an upper nor lower bound. But having a better shuffle list isn’t enough not to enter an infinite loop.

    +

    Generally, we cannot decide whether filter (<x) xs is empty. Then to resolve this problem, I’ll authorize some error in the creation of our binary tree. This new version of code can create binary tree which don’t have the following property for some of its nodes:

    +
    +

    Any element of the left (resp. right) branch must all be strictly inferior (resp. superior) to the label of the root.

    +
    +

    Remark it will remains mostly an ordered binary tree. Furthermore, by construction, each node value is unique in the tree.

    +

    Here is our new version of treeFromList. We simply have replaced filter by safefilter.

    +
    +
    treeFromList :: (Ord a, Show a) => [a] -> BinTree a
    +treeFromList []    = Empty
    +treeFromList (x:xs) = Node x left right
    +          where 
    +              left = treeFromList $ safefilter (<x) xs
    +              right = treeFromList $ safefilter (>x) xs
    +
    +

    This new function safefilter is almost equivalent to filter but don’t enter infinite loop if the result is a finite list. If it cannot find an element for which the test is true after 10000 consecutive steps, then it considers to be the end of the search.

    +
    +
    safefilter :: (a -> Bool) -> [a] -> [a]
    +safefilter f l = safefilter' f l nbTry
    +  where
    +      nbTry = 10000
    +      safefilter' _ _ 0 = []
    +      safefilter' _ [] _ = []
    +      safefilter' f (x:xs) n = 
    +                  if f x 
    +                     then x : safefilter' f xs nbTry 
    +                     else safefilter' f xs (n-1) 
    +
    +

    Now run the program and be happy:

    +
    +
    main = do
    +      putStrLn "take 10 shuffle"
    +      print $ take 10 shuffle
    +      putStrLn "\ntreeTakeDepth 8 (treeFromList shuffle)"
    +      print $ treeTakeDepth 8 (treeFromList $ shuffle)
    +
    +

    You should realize the time to print each value is different. This is because Haskell compute each value when it needs it. And in this case, this is when asked to print it on the screen.

    +

    Impressively enough, try to replace the depth from 8 to 100. It will work without killing your RAM! The flow and the memory management is done naturally by Haskell.

    +

    Left as an exercise to the reader:

    +
      +
    • Even with large constant value for deep and nbTry, it seems to work nicely. But in the worst case, it can be exponential. Create a worst case list to give as parameter to treeFromList.
      hint: think about ([0,-1,-1,....,-1,1,-1,...,-1,1,...]).
    • +
    • I first tried to implement safefilter as follow: +
      +  safefilter' f l = if filter f (take 10000 l) == []
      +                    then []
      +                    else filter f l
      +  
      + +Explain why it doesn’t work and can enter into an infinite loop.
    • +
    • Suppose that shuffle is real random list with growing bounds. If you study a bit this structure, you’ll discover that with probability 1, this structure is finite. Using the following code (suppose we could use safefilter' directly as if was not in the where of safefilter) find a definition of f such that with probability 1, treeFromList’ shuffle is infinite. And prove it. Disclaimer, this is only a conjecture.
    • +
    +
    treeFromList' []  n = Empty
    +treeFromList' (x:xs) n = Node x left right
    +    where
    +        left = treeFromList' (safefilter' (<x) xs (f n)
    +        right = treeFromList' (safefilter' (>x) xs (f n)
    +        f = ???
    +

    04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs

    +

    Thanks

    +

    Thanks to /r/haskell and /r/programming. Your comment were most than welcome.

    +

    Particularly, I want to thank Emm a thousand times for the time he spent on correcting my English. Thank you man.

    +
    +
    +
      +
    1. Même si tous les langages récents essayent de les cacher, ils restent présents.

    2. +
    3. I know I’m cheating. But I will talk about non-strict later.

    4. +
    5. For the brave, a more complete explanation of pattern matching can be found here.

    6. +
    7. You should remark squareEvenSum'' is more efficient that the two other versions. The order of (.) is important.

    8. +
    9. Which itself is very similar to the javascript eval on a string containing JSON).

    10. +
    11. There are some unsafe exceptions to this rule. But you shouldn’t see such use on a real application except maybe for debugging purpose.

    12. +
    13. For the curious the real type is data IO a = IO {unIO :: State# RealWorld -> (# State# RealWorld, a #)}. All the # as to do with optimisation and I swapped the fields in my example. But mostly, the idea is exactly the same.

    14. +
    15. Well, you’ll certainly need to practice a bit to get used to them and to understand when you can use them and create your own. But you already made a big step in this direction.

    16. +
    +
    ]]>
    +
    + + La typography et le Web + + http://yannesposito.com/Scratch/fr/blog/Typography-and-the-Web/index.html + 2012-02-02T00:00:00Z + 2012-02-02T00:00:00Z +

    +
    + +

    tlpl: La typography sur le web est pourrie et nous ne somme pas près de voir ce problème réparé.

    +
    + +

    Je suis tombé sur ce site: open typography. Leur message principal est :

    +
    +

    «There is no reason to wait for browser development to catch up. We can all create better web typography ourselves, today.»

    +
    +

    ou en français :

    +
    +

    «Nous ne somme pas obligé d’attendre le développement des navigateurs. Nous pouvons créer un web avec une meilleure typographie aujourd’hui.»

    +
    +

    Comme quelqu’un qui a déjà essayé d’améliorer la typographie de son site web, et en particulier des ligatures, je crois que c’est faux.

    +

    J’ai déjà écrit un système automatique qui détecte et ajoute des ligatures en utilisant des caractères unicode. Cependant je n’ai jamais publié cette amélioration sur le web et voilà pourquoi :

    +

    Tout d’abord, qu’est-ce qu’un ligature ?

    +

    +

    Quel est le problème des ligatures sur le web ? Le premier c’est que vous ne pouvez pas chercher les mots qui contiennent ces ligatures. Par exemple essayez de chercher le mot “first”.

    +
      +
    • first ← Pas de ligature, pas de problème1.
    • +
    • r ← Une jolie ligature, mais introuvable avec une recherche (C-f).
    • +
    +

    Le second problème est le rendu. Par exemple, essayer d’utiliser un charactère de ligature en petites capitales :

    +
      +
    • first
    • +
    • r
    • +
    +

    Voici une capture d’écran pour que vous voyez ce que je vois :

    +

    +

    Le navigateur est incapable de comprendre que le caractère de ligature “” doit être rendu comme fi lorsqu’il est en petites capitales. Et une part du problème est que l’on peut décider d’écrire en petite majuscule dans le css.

    +

    Comment par exemple utiliser un charactère de ligature unicode sur un site qui possède différents rendus via différentes css ?

    +

    Comparons à LaTeX

    +

    +

    Si vous faites attention au détail, vous constaterez que le premier “first” contient une ligature. Bien entendu la deuxième ligne est affichée correctement. Le code que j’ai utilisé pour avoir ce rendu est simplement :

    +
    \item first
    +\item {\sc first}
    +

    LaTeX a été suffisamment intelligent pour créer les ligatures si nécessaire.

    +

    La ligature “” est rare et n’est pas rendu par défaut par LaTeX. Si vous voulez voir des ligatures rares, vous pouvez utiliser XƎLaTeX:

    +

    XeLaTeX ligatures

    +

    J’ai copié cette image de l’excellent article de Dario Taraborelli.

    +

    Clairement il sera difficile aux navigateurs de corriger ces problèmes. Imaginez le nombre de petites exceptions.

    +
      +
    • Le texte est en petites capitales, je ne dois pas utiliser de ligatures.
    • +
    • Le mot courant contient un caractère de ligature, je ne dois pas chercher d’autre ligature dans ce mot.
    • +
    • La fonte n’a pas défini de caractère unicode pour la ligature, je ne dois pas l’utiliser.
    • +
    • Une commande javascript a modifé le CSS, je dois vérifier si je dois remplacer les ligatures par les deux caractères.
    • +
    • etc…
    • +
    +

    Dans tous les cas, si quelqu’un possède une solution je suis preneur !

    +
    +
    +
      +
    1. En réalité, vous devriez pouvoir voir une ligature. Maintenant j’utilise : text-rendering: optimizelegibility. Le rendu est correct parce que j’utilise une fonte correct, à savoir Computer Modern de Donald Knuth.

    2. +
    +
    ]]>
    +
    + + Site en Haskell + + http://yannesposito.com/Scratch/fr/blog/Yesod-tutorial-for-newbies/index.html + 2012-01-15T00:00:00Z + 2012-01-15T00:00:00Z + Neo Flying at warp speed

    +
    + +

    mise à jour: mise à jour pour la version 0.10 de yesod.

    +

    tlpl: Un tutoriel pour yesod, un framework web Haskell. Vous ne devriez pas avoir besoin de savoir programmer en Haskell. Par contre je suis désolé pour les francophones, mais je n’ai pas eu le courage de traduire cet article en Français.

    +
    +
    +Table of content +
    + +
      +
    • Table of Content (generated) {:toc}
    • +
    +
    +
    + +

    Why Haskell?

    +

    Impressive Haskell Benchmark

    +

    Its efficiency (see [Snap Benchmark][snapbench] & Warp Benchmark[^benchmarkdigression]). Haskell is an order of magnitude faster than interpreted languages like [Ruby][haskellvsruby] and [Python][haskellvspython][^speeddigression].

    +

    Haskell is a high level language and make it harder to shoot you in the foot than C, C++ or Java for example. One of the best property of Haskell being:

    +
    +

    “If your program compile it will be very close to what the programmer intended”.

    +
    +

    Haskell web frameworks handle parallel tasks perfectly. For example even better than node.js[^nodejstroll].

    +

    Thousands of Agent Smith

    +

    From the pure technical point of view, Haskell seems to be the perfect web development tool. Weaknesses of Haskell certainly won’t be technical:

    +
      +
    • Hard to grasp Haskell
    • +
    • Hard to find a Haskell programmer
    • +
    • The Haskell community is smaller than the community for /.*/
    • +
    • There is no heroku for Haskell (even if Greg Weber did it, it was more a workaround).
    • +
    +

    I won’t say these are not important drawbacks. But, with Haskell your web application will have both properties to absorb an impressive number of parallel request securely and to adapt to change.

    +

    Actually there are three main Haskell web frameworks:

    +
      +
    1. Happstack
    2. +
    3. Snap
    4. +
    5. Yesod
    6. +
    +

    I don’t think there is a real winner between these three framework. The choice I made for yesod is highly subjective. I just lurked a bit and tried some tutorials. I had the feeling yesod make a better job at helping newcomers. Furthermore, apparently the yesod team seems the most active. Of course I might be wrong since it is a matter of feeling.

    +

    1. Draw some circles. 2. Draw the rest of the fucking owl

    +

    Why did I write this article? The yesod documentation and particularly the book are excellent. But I missed an intermediate tutorial. This tutorial won’t explain all details. I tried to give a step by step of how to start from a five minute tutorial to an almost production ready architecture. Furthermore explaining something to others is a great way to learn. If you are used to Haskell and Yesod, this tutorial won’t learn you much. If you are completely new to Haskell and Yesod it might hopefully helps you. Also if you find yourself too confused by the syntax, it might helps to read this article

    +

    During this tutorial you’ll install, initialize and configure your first yesod project. Then there is a very minimal 5 minutes yesod tutorial to heat up and verify the awesomeness of yesod. Then we will clean up the 5 minutes tutorial to use some “best practices”. Finally there will be a more standard real world example; a minimal blog system.

    +

    [snapbench]: http://snapframework.com/blog/2010/11/17/snap-0.3-benchmarks [^benchmarkdigression]: One can argue these benchmark contains many problems. But the benchmarks are just here to give an order of idea. Mainly Haskell is very fast. [^speeddigression]: Generally high level Haskell is slower than C, but low level Haskell is equivalent to C speed. It means that even if you can easily link C code with Haskell, this is not needed to reach the same speed. Furthermore writing a web service in C/C++ seems to be a very bad idea. You can take a look at a discussion on HN about this. [^nodejstroll]: If you are curious, you can search about the Fibonacci node.js troll. Without any tweaking, Haskell handled this problem perfectly. I tested it myself using yesod instead of Snap. [haskellvsruby]: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=ghc&lang2=yarv [haskellvspython]: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=ghc&lang2=python3

    +

    Before the real start

    +

    Install

    +

    The recommended way to install Haskell is to download the Haskell Platform.

    +

    Once done, you need to install yesod. Open a terminal session and do:

    +
    ~ cabal update
    +~ cabal install yesod cabal-dev
    +

    There are few steps but it should take some time to finish.

    +

    Initialize

    +

    You are now ready to initialize your first yesod project. Open a terminal and type:

    +
    ~ yesod init
    +

    Enter your name, choose yosog for the project name and enter Yosog for the name of the Foundation. Finally choose sqlite. Now, start the development cycle:

    +
    ~ cd yosog
    +~ cabal-dev install && yesod --dev devel
    +

    This will compile the entire project. Be patient it could take a while the first time. Once finished a server is launched and you could visit it by clicking this link:

    +

    http://localhost:3000

    +

    Congratulation! Yesod works!

    +
    + +

    Note: if something is messed up use the following command line inside the project directory.

    +
    \rm -rf dist/* ; cabal-dev install && yesod --dev devel
    +
    + +

    Until the end of the tutorial, use another terminal and let this one open in a corner to see what occurs.

    +

    Configure git

    +
    +

    Of course this step is not mandatory for the tutorial but it is a good practice.

    +
    +

    Copy this .gitignore file into the yosog folder.

    +
    cabal-dev
    +dist
    +.static-cache
    +static/tmp
    +*.sqlite3
    +

    Then initialize your git repository:

    +
    ~ git init .
    +~ git add .
    +~ git commit -a -m "Initial yesod commit"
    +

    We are almost ready to start.

    +

    Some last minute words

    +

    Up until here, we have a directory containing a bunch of files and a local web server listening the port 3000. If we modify a file inside this directory, yesod should try to recompile as fast as possible the site. Instead of explaining the role of every file, let’s focus only on the important files/directories for this tutorial:

    +
      +
    1. config/routes
    2. +
    3. Handler/
    4. +
    5. templates/
    6. +
    7. config/models
    8. +
    +

    Obviously:

    +

    config/routes | is where you’ll configure the map %url → Code. |
    Handler/ | contains the files that will contain the code called when a %url is accessed. |
    templates/ | contains html, js and css templates. |
    config/models | is where you’ll configure the persistent objects (database tables). |

    +

    During this tutorial we’ll modify other files as well, but we won’t explore them in detail.

    +

    Also note, shell commands are executed in the root directory of your project instead specified otherwise.

    +

    We are now ready to start!

    +

    Echo

    +

    To verify the quality of the security of the yesod framework, let’s make a minimal echo application.

    +
    +

    Goal:

    +

    Make a server that when accessed /echo/[some text] should return a web page containing “some text” inside an h1 bloc.

    +
    +

    In a first time, we must declare the %url of the form /echo/... are meaningful. Let’s take a look at the file config/routes:

    +
    +/static StaticR Static getStatic
    +/auth   AuthR   Auth   getAuth
    +
    +/favicon.ico FaviconR GET
    +/robots.txt RobotsR GET
    +
    +/ HomeR GET
    +
    + +

    We want to add a route of the form /echo/[anything] somehow and do some action with this. Add the following:

    +
    +/echo/#String EchoR GET
    +
    + +

    This line contains three elements: the %url pattern, a handler name, an %http method. I am not particularly fan of the big R notation but this is the standard convention.

    +

    If you save config/routes, you should see your terminal in which you launched yesod devel activate and certainly displaying an error message.

    +
    +Application.hs:31:1: Not in scope: `getEchoR'
    +
    + +

    Why? Simply because we didn’t written the code for the handler EchoR. Edit the file Handler/Home.hs and append this:

    +
    getEchoR :: String -> Handler RepHtml
    +getEchoR theText = do
    +    defaultLayout $ do
    +        [whamlet|<h1>#{theText}|]
    +

    Don’t worry if you find all of this a bit cryptic. In short it just declare a function named getEchoR with one argument (theText) of type String. When this function is called, it return a Handler RepHtml whatever it is. But mainly this will encapsulate our expected result inside an html text.

    +

    After saving the file, you should see yesod recompile the application. When the compilation is finished you’ll see the message: Starting devel application.

    +

    Now you can visit: http://localhost:3000/echo/Yesod%20rocks!

    +

    TADA! It works!

    +

    Bulletproof?

    +

    Neo stops a myriad of bullets

    +

    Even this extremely minimal web application has some impressive properties. For exemple, imagine an attacker entering this %url:

    +[http://localhost:3000/echo/<a>I'm <script>alert("Bad!");](http://localhost:3000/echo/I’m + +

    " %>

    +

    The special characters are protected for us. A malicious user could not hide some bad script inside.

    +

    This behavior is a direct consequence of type safety. The %url string is put inside a %url type. Then the interesting part in the %url is put inside a String type. To pass from %url type to String type some transformation are made. For example, replace all “%20” by space characters. Then to show the String inside an html document, the string is put inside an html type. Some transformations occurs like replace “<” by “&lt;”. Thanks to yesod, this tedious job is done for us.

    +
    "http://localhost:3000/echo/some%20text<a>" :: URL
    +                    ↓
    +              "some text<a>"                 :: String
    +                    ↓
    +          "some text &amp;lt;a&amp;gt;"             :: Html 
    +

    Yesod is not only fast, it helps us to remain secure. It protects us from many common errors in other paradigms. Yes, I am looking at you PHP!

    +

    Cleaning up

    +

    Even this very minimal example should be enhanced. We will clean up many details:

    +
    +

    Use a better css

    +

    It is nice to note, the default template is based on %html5 boilerplate. Let’s change the default css. Add a file named default-layout.lucius inside the templates/ directory containing:

    +
    body {
    +    font-family: Helvetica, sans-serif; 
    +    font-size: 18px; }
    +#main {
    +    padding: 1em;
    +    border: #CCC solid 2px;
    +    border-radius: 5px;
    +    margin: 1em;
    +    width: 37em;
    +    margin: 1em auto;
    +    background: #F2F2F2;
    +    line-height: 1.5em;
    +    color: #333; }
    +.required { margin: 1em 0; }
    +.optional { margin: 1em 0; }
    +label { width: 8em; display: inline-block; }
    +input, textarea { background: #FAFAFA}
    +textarea { width: 27em; height: 9em;}
    +ul { list-style: square; }
    +a { color: #A56; }
    +a:hover { color: #C58; }
    +a:active { color: #C58; }
    +a:visited { color: #943; }
    +

    Personally I would prefer if such a minimal css was put with the scaffolding tool. I am sure somebody already made such a minimal css which give the impression the browser handle correctly html without any style applied to it. But I digress.

    +

    Separate Handlers

    +

    Generally you don’t want to have all your code inside a unique file. This is why we will separate our handlers. In a first time create a new file Handler/Echo.hs containing:

    +
    module Handler.Echo where
    +
    +import Import
    +
    +getEchoR :: String -> Handler RepHtml
    +getEchoR theText = do
    +    defaultLayout $ do
    +        [whamlet|<h1>#{theText}|]
    +

    Do not forget to remove the getEchoR function inside Handler/Home.hs.

    +

    We must declare this new file intoyosog.cabal. Just after Handler.Home, add:

    +
    +    Handler.Echo
    +
    + +

    We must also declare this new Handler module inside Application.hs. Just after the “import Handler.Home”, add:

    +
    import Handler.Echo
    +

    This is it.

    +

    ps: I am sure not so far in the future we could simply write yesod add-handler Echo to declare it and create a new handler file.

    +

    Data.Text

    +

    It is a good practice to use Data.Text instead of String.

    +

    To declare it, add this import directive to Foundation.hs (just after the last one):

    +
    import Data.Text
    +

    We have to modify config/routes and our handler accordingly. Replace #String by #Text in config/routes:

    +
    +/echo/#Text EchoR GET
    +
    + +

    And do the same in Handler/Echo.hs:

    +
    module Handler.Echo where
    +
    +import Import
    +
    +getEchoR :: Text -> Handler RepHtml
    +getEchoR theText = do
    +    defaultLayout $ do
    +        [whamlet|<h1>#{theText}|]
    +

    Use templates

    +

    Some html (more precisely hamlet) is written directly inside our handler. We should put this part inside another file. Create the new file templates/echo.hamlet containing:

    +
    <h1> #{theText}
    +

    and modify the handler Handler/Echo.hs:

    +
    getEchoR :: Text -> Handler RepHtml
    +getEchoR theText = do
    +    defaultLayout $ do
    +        $(widgetFile "echo")
    +

    At this point, our web application is structured between different files. Handler are grouped, we use Data.Text and our views are in templates. It is the time to make a slightly more complex example.

    +

    Mirror

    +

    Neo touching a mirror

    +

    Let’s make another minimal application. You should see a form containing a text field and a validation button. When you enter some text (for example “Jormungad”) and validate, the next page present you the content and its reverse appended to it. In our example it should return “JormungaddagnumroJ”.

    +

    First, add a new route:

    +
    +/mirror MirrorR GET POST
    +
    + +

    This time the path /mirror will accept GET and POST requests. Add the corresponding new Handler file:

    +
    module Handler.Mirror where
    +
    +import Import
    +import qualified Data.Text as T
    +
    +getMirrorR :: Handler RepHtml
    +getMirrorR = do
    +    defaultLayout $ do
    +        $(widgetFile "mirror")
    +
    +postMirrorR :: Handler RepHtml
    +postMirrorR =  do
    +        postedText <- runInputPost $ ireq textField "content"
    +        defaultLayout $ do
    +            $(widgetFile "posted")
    +

    Don’t forget to declare it inside yosog.cabal and Application.hs.

    +

    We will need to use the reverse function provided by Data.Text which explain the additional import.

    +

    The only new thing here is the line that get the POST parameter named “content”. If you want to know more detail about it and form in general you can take look at the yesod book.

    +

    Create the two corresponding templates:

    +
    <h1> Enter your text
    +<form method=post action=@{MirrorR}>
    +    <input type=text name=content>
    +    <input type=submit>
    +
    <h1>You've just posted
    +<p>#{postedText}#{T.reverse postedText}
    +<hr>
    +<p><a href=@{MirrorR}>Get back
    +

    And that is all. This time, we won’t need to clean up. We may have used another way to generate the form but we’ll see this in the next section.

    +

    Just try it by clicking here.

    +

    Also you can try to enter strange values. Like before, your application is quite secure.

    +

    A Blog

    +

    We saw how to retrieve %http parameters. It is the time to save things into a database.

    +

    As before add some routes inside config/routes:

    +
    +/blog               BlogR       GET POST
    +/blog/#ArticleId    ArticleR    GET
    +
    + +

    This example will be very minimal:

    +
      +
    • GET on /blog should display the list of articles.
    • +
    • POST on /blog should create a new article
    • +
    • GET on /blog/<article id> should display the content of the article.
    • +
    +

    First we declare another model object. Append the following content to config/models:

    +
    +Article
    +    title   Text
    +    content Html 
    +    deriving
    +
    + +

    As Html is not an instance of Read, Show and Eq, we had to add the deriving line. If you forget it, there will be an error.

    +

    After the route and the model, we write the handler. First, declare a new Handler module. Add import Handler.Blog inside Application.hs and add it into yosog.cabal. Let’s write the content of Handler/Blog.hs. We start by declaring the module and by importing some block necessary to handle Html in forms.

    +
    module Handler.Blog
    +    ( getBlogR
    +    , postBlogR
    +    , getArticleR
    +    )
    +where
    +
    +import Import
    +import Data.Monoid
    +
    +-- to use Html into forms
    +import Yesod.Form.Nic (YesodNic, nicHtmlField)
    +instance YesodNic App
    +

    Remark: it is a best practice to add the YesodNic instance inside Foundation.hs. I put this definition here to make things easier but you should see a warning about this orphan instance. To put the include inside Foundation.hs is left as an exercice to the reader.

    +

    Hint: Do not forget to put YesodNic and nicHtmlField inside the exported objects of the module.

    +
    entryForm :: Form Article
    +entryForm = renderDivs $ Article
    +    <$> areq   textField "Title" Nothing
    +    <*> areq   nicHtmlField "Content" Nothing
    +

    This function defines a form for adding a new article. Don’t pay attention to all the syntax. If you are curious you can take a look at Applicative Functor. You just have to remember areq is for required form input. Its arguments being: areq type label default_value.

    +
    -- The view showing the list of articles
    +getBlogR :: Handler RepHtml
    +getBlogR = do
    +    -- Get the list of articles inside the database.
    +    articles <- runDB $ selectList [] [Desc ArticleTitle]
    +    -- We'll need the two "objects": articleWidget and enctype
    +    -- to construct the form (see templates/articles.hamlet).
    +    (articleWidget, enctype) <- generateFormPost entryForm
    +    defaultLayout $ do
    +        $(widgetFile "articles")
    +

    This handler should display a list of articles. We get the list from the DB and we construct the form. Just take a look at the corresponding template:

    +
    <h1> Articles
    +$if null articles
    +    -- Show a standard message if there is no article
    +    <p> There are no articles in the blog
    +$else
    +    -- Show the list of articles
    +    <ul>
    +        $forall Entity articleId article <- articles
    +            <li> 
    +                <a href=@{ArticleR articleId} > #{articleTitle article}
    +<hr>
    +  <form method=post enctype=#{enctype}>
    +    ^{articleWidget}
    +    <div>
    +        <input type=submit value="Post New Article">
    +

    You should remark we added some logic inside the template. There is a test and a “loop”.

    +

    Another very interesting part is the creation of the form. The articleWidget was created by yesod. We have given him the right parameters (input required or optional, labels, default values). And now we have a protected form made for us. But we have to create the submit button.

    +

    Get back to Handler/Blog.hs.

    +
    -- we continue Handler/Blog.hs
    +postBlogR :: Handler RepHtml
    +postBlogR = do
    +    ((res,articleWidget),enctype) <- runFormPost entryForm
    +    case res of 
    +         FormSuccess article -> do 
    +            articleId <- runDB $ insert article
    +            setMessage $ toHtml $ (articleTitle article) <> " created"
    +            redirect $ ArticleR articleId 
    +         _ -> defaultLayout $ do
    +                setTitle "Please correct your entry form"
    +                $(widgetFile "articleAddError")
    +

    This function should be used to create a new article. We handle the form response. If there is an error we display an error page. For example if we left some required value blank. If things goes right:

    +
      +
    • we add the new article inside the DB (runDB $ insert article)
    • +
    • we add a message to be displayed (setMessage $ ...)
    • +
    • we are redirected to the article web page.
    • +
    +

    Here is the content of the error Page:

    +
    <form method=post enctype=#{enctype}>
    +    ^{articleWidget}
    +    <div>
    +        <input type=submit value="Post New Article">
    +

    Finally we need to display an article:

    +
    getArticleR :: ArticleId -> Handler RepHtml
    +getArticleR articleId = do
    +    article <- runDB $ get404 articleId
    +    defaultLayout $ do
    +        setTitle $ toHtml $ articleTitle article
    +        $(widgetFile "article")
    +

    The get404 function try to do a get on the DB. If it fails it return a 404 page. The rest should be clear. Here is the content of templates/article.hamlet:

    +
    <h1> #{articleTitle article}
    +<article> #{articleContent article}
    +

    The blog system is finished. Just for fun, you can try to create an article with the following content:

    +
    <p>A last try to <em>cross script</em> 
    +   and <em>SQL injection</em></p>
    +<p>Here is the first try: 
    +   <script>alert("You loose");</script></p>
    +<p> And Here is the last </p>
    +"); DROP TABLE ARTICLE;;
    +

    Conclusion

    +

    This is the end of this tutorial. I made it very minimal.

    +

    If you already know Haskell and you want to go further, you should take a look at the recent i18n blog tutorial. It will be obvious I inspired my own tutorial on it. You’ll learn in a very straightforward way how easy it is to use authorizations, Time and internationalization.

    +

    If, on the other hand you don’t know Haskell. Then you shouldn’t jump directly to web programming. Haskell is a very complex and unusual language. My advice to go as fast as possible in using Haskell for web programming is:

    +
      +
    1. Start by try Haskell in your browser
    2. +
    3. Then read the excellent Learn you a Haskell for Great Good
    4. +
    5. If you have difficulties in understanding concepts like monads, you should really read these articles. For me they were enlightening.
    6. +
    7. If you feel confident, you should be able to follows the yesod book and if you find difficult to follows the yesod book, you should read real world Haskell first (it is a must read).
    8. +
    +

    Also, note that:

    +
      +
    • haskell.org is full of excellent resources.
    • +
    • hoogle will be very useful
    • +
    • Use hlint as soon as possible to get good habits.
    • +
    +

    As you should see, if you don’t already know Haskell, the path is long but I guaranty you it will be very rewarding!

    +

    ps: You can download the source of this yesod blog tutorial at github.com/yogsototh/yosog.

    +
    +
    +
      +
    1. By view I mean yesod widget’s hamlet, lucius and julius files.

    2. +
    +
    ]]>
    +
    + + Accroître le pouvoir des languages déficients. + + http://yannesposito.com/Scratch/fr/blog/SVG-and-m4-fractals/index.html + 2011-10-20T00:00:00Z + 2011-10-20T00:00:00Z + Yesod logo made in SVG and m4

    +
    + +

    tlpl: Utiliser m4 pour accroître le pouvoir d’xslt et d’svg. Example cool, les fractales.

    +
    + +

    Lorsqu’xml fût inventé beaucoup pensaient que c’était l’avenir. Passer de fichiers plat à des fichiers structurés standardisés fût un grand progrès dans beaucoup de domaines. Cerain se mirent à voir du xml de partout. À tel point que les les format compatibles xml naquirent de toute part. Non seulement comme format de fichier, mais aussi comme format pour un langage de programmation.

    +

    Ô joie !

    +

    Malheureusement, xml fût fabriquer pour le transfert de données. Pas du tout pour être vu ou édité directement. La triste vérité est qu’xml est verbeux et laid. Dans un monde parfait, nous ne devrions avoir des programmes qui s’occupent de nous afficher correctement le xml pour nous épargner la peine de les voir directement. Mais devinez quoi ? Notre monde n’est pas parfait. Beaucoup de programmeurs sont ainsi forcé de travailler directement avec de l’xml.

    +

    xml, n’est pas le seul cas de format mal utilisé que je connaisse. Vous avez d’autres formats dans lesquels il serait très agréable d’ajouter des variables, des boucles, des fonctions…

    +

    Mais je suis là pour vous aider. Si comme moi vous détestez xslt ou écrire de l’xml. Je vais vous montrer une façon d’améliorer tout ça.

    +

    Un exemple avec xslt

    +

    Commençons avec le pire cas de langage xml que je connaisse : xslt. Tous les développeurs qui ont déjà dû écrire du xslt savent à quel point ce langage est horrible.

    +

    Pour réduire la “verbosité” de tels langages, il y a un moyen. m4. Oui, le préprocesseur utilisé par C et C++.

    +

    Voici certains exemples :

    +
      +
    • Les variables, au lieu d’écrire myvar = value, voici la version xslt :
    • +
    +
    <xsl:variable name="myvar" select="value"/>
    +
      +
    • Afficher quelquechose. Au lieu de print "Hello world!", xslt nous offre :
    • +
    +
    <xsl:text 
    +    disable-output-escaping="yes"><![CDATA[Hello world!
    +]]></xsl:text>
    +
      +
    • afficher la valeur d’une variable, au lieu de print myvar, nous avons droit à :
    • +
    +
    <xslt:value-of select="myvar"/>
    +
      +
    • Essayez d’imaginer à quel point il est verbeux de déclarer une fonction dans ce langage.
    • +
    +

    La solution (m4 à la rescousse)

    +
    <?xml version="1.0" standalone="yes"?> <!-- YES its <span class="sc">xml</span> -->
    +<!-- ← start a comment, then write some m4 directives:
    +
    +define(`ydef',`<xsl:variable name="$1" select="$2"/>')
    +define(`yprint',`<xsl:text disable-output-escaping="yes"><![CDATA[$1]]></xsl:text>')
    +define(`yshow',`<xsl:value-of select="$1"/>')
    +
    +-->
    +<!-- Yes, <span class="sc">xml</span> sucks to be read -->
    +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    +<!-- And it sucks even more to edit -->
    +<xsl:template match="/">
    +    ydef(myvar,value)
    +    yprint(Hello world!)
    +    yshow(myvar)
    +</xsl:template>
    +

    Maintenant compilons simplement ce fichier :

    +
    m4 myfile.m4 > myfile.xslt
    +

    Et vous pouvez profitez ! Maintenant xslt devient plus lisible et plus facile à éditer.

    +

    La partie la plus cool: les fractales !

    +

    À ses débuts, beaucoup pensaient que ce serait le nouveau Flash. Apparemment, ce devrait plutôt être canvas avec du javascript qui occupera cette place.

    +

    Tout d’abord, laissez moi vous montrer le résultat :

    +

    Yesod logo made in SVG and m4 Cliquez sur l’image pour voir le svg directement. Attention, si vous n’avez pas un ordinateur récent, ça risque de ramer.

    +

    Le positionnement du texte “esod” par rapport au “λ” renversé a été en jouant avec firebug. De cette façon je n’avais pas à regénérer pour tester.

    +

    Faire une telle fractale revient à :

    +
      +
    1. Choisir un élément racine ;
    2. +
    3. le dupliquer et le transformer ;
    4. +
    5. le résultat est un nouveau sous-élément ;
    6. +
    7. répéter à partir de 2 mais en utilisant le sous-élément comme nouvelle racine.
    8. +
    9. Arréter lorsque la récursion est assez profonde.
    10. +
    +

    Si j’avais dû faire ça manuellement, il m’aurait fallu faire beaucoup de copier/coller dans mon svg. Simplement parce que la transformation est toujours la même, mais je ne pouvais pas dire, utiliser la transformation appelée “titi”. Plutôt que copier du xml, j’ai utilisé m4.

    +

    Et voici le code commenté :

    +
    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    +<!--
    +     M4 Macros
    +define(`YTRANSFORMONE', `scale(.43) translate(-120,-69) rotate(-10)')
    +define(`YTRANSFORMTWO', `scale(.43) translate(-9,-67.5) rotate(10)')
    +define(`YTRANSFORMTHREE', `scale(.43) translate(53,41) rotate(120)')
    +define(`YGENTRANSFORM', `translate(364,274) scale(3)')
    +define(`YTRANSCOMPLETE', `
    +    <g id="level_$1">
    +        <use style="opacity: .8" transform="YTRANSFORMONE" xlink:href="#level_$2" />
    +        <use style="opacity: .8" transform="YTRANSFORMTWO" xlink:href="#level_$2" />
    +        <use style="opacity: .8" transform="YTRANSFORMTHREE" xlink:href="#level_$2" />
    +    </g>
    +    <use transform="YGENTRANSFORM" xlink:href="#level_$1" />
    +')
    + -->
    +<svg 
    +    xmlns="http://www.w3.org/2000/svg" 
    +    xmlns:xlink="http://www.w3.org/1999/xlink"
    +    x="64" y="64" width="512" height="512" viewBox="64 64 512 512"
    +    id="svg2" version="1.1">
    +    <g id="level_0"> <!-- some group, if I want to add other elements -->
    +        <!-- the text "λ" -->
    +        <text id="lambda" 
    +            fill="#333" style="font-family:Ubuntu; font-size: 100px"
    +            transform="rotate(180)">λ</text>
    +    </g>
    +    <!-- the text "esod" -->
    +    <text 
    +        fill="#333" 
    +        style="font-family:Ubuntu; font-size: 28px; letter-spacing: -0.10em" 
    +        x="-17.3" 
    +        y="69" 
    +        transform="YGENTRANSFORM">esod</text>
    +    <!-- ROOT ELEMENT -->
    +    <use transform="YGENTRANSFORM" xlink:href="#level_0" />
    +
    +    YTRANSCOMPLETE(1,0) <!-- First recursion -->
    +    YTRANSCOMPLETE(2,1) <!-- deeper -->
    +    YTRANSCOMPLETE(3,2) <!-- deeper -->
    +    YTRANSCOMPLETE(4,3) <!-- even deeper -->
    +    YTRANSCOMPLETE(5,4) <!-- Five level seems enough -->
    +</svg>
    +

    et je l’ai compile en svg et ensuite en png avec :

    +
    m4 yesodlogo.m4 > yesodlogo.svg && convert yesodlogo.svg yesodlogo.png
    +

    Le λ est dupliqué avec trois “transformations” différentes. Les transformations sont : YTRANSFORMONE, YTRANSFORMTWO et YTRANSFORMTHREE.

    +

    Chaque transformation est une similarité (translation + rotation + zoom, ce qui est équivalent à juste rotation + zoom, mais bon).

    +

    Une fois fixée chaque transformation peut ensuite être réutilisée pour chaque nouveau niveau.

    +

    Maintenant YTRANSCOMPLETE entre en jeu. Cette macro prend deux arguments. Le niveau courant et le niveau précédent. Cette macro va dupliquer le niveau précédent en lui appliquant chacune des 3 transformations. Au niveau 0, le contenu est un seul grand λ, le niveau 1 en contient 3. Le niveau 2 en contient 9, etc… Le niveau 5 contient 35=243 λ. Tous les niveaux combinés représentent 36-1 / 2 = 364 λ.

    +

    L’avantage principal c’est que je pouvais visualiser le résultat final facilement. Sans ce système de macro, pour faire une preview il m’aurait fallu faire des copier/coller + quelques modifications à chaque essai.

    +

    Conclusion

    +

    Ce fut très amusant de faire une fractale en svg, mais la partie la plus intéressante était d’augmenter la puissance d’expressivité du langage en utilise un préprocesseur. J’ai utilisé cette méthode avec xslt pour une vrai application par exemple. On peut aussi utiliser m4 pour faire des includes d’autres fichiers. Typiquement je l’ai utiliser pour les includes dans un format obscur. Mais vous pouvez aussi le considérer pour des includes dans du HTML. Par exemple pour fabriquer un site statique rapidement, m4 peut se révéler utile pour inclure un footer ou un menu sur toutes les pages par exemple. J’ai aussi pensé que l’on pouvait utiliser m4 pour structurer des programmes comme brainfuck.

    ]]>
    +
    + + Les idées de yesod + + http://yannesposito.com/Scratch/fr/blog/Yesod-excellent-ideas/index.html + 2011-10-04T00:00:00Z + 2011-10-04T00:00:00Z + Title image

    +
    + +

    tlpl:

    +

    Cela fait un moment que je suis la progression du framework yesod. À mon humble avis on peut commencer à l’utiliser pour des applications sérieuses (comprendre en prod). Avant de vous dire pourquoi vous devriez aussi le considérer, je préfère vous parler de bonnes idées (parmi d’autres) introduites par yesod que je n’avais jamais vu ailleurs.

    +
    + +

    Types saufs

    +

    Commençons par une BD d’xkcd :

    +
    +SQL injection by a mom

    SQL injection by a mom

    +
    +

    Lorsque vous créez une application web, beaucoup de temps est passé à s’occuper de chaînes de caractères. Des chaînes de caractère pour les URL, le HTML, le Javascript, les CSS, les requêtes SQL, etc… Pour éviter des utilisation malicieuses vous devez protéger chaque chaîne de caractère entre chaque étape. Par exemple supposons que vous entriez comme nom :

    +
    Newton<script>alert("An apple fall")</script>
    +

    Sans une protection correcte, le message “An apple fall” sera affiché à chaque fois que quelqu’un essayera d’accéder au nom de cet utilisateur. Les “types saufs” sont le tonyglandil du web. A chaque chaine de caractère, on lui associe un “type”. A quoi sert cette chaîne de caractère ? Est-ce une URL ? Du javascript ? De l’HTML ? Entre chaque passage d’une représentation à une autre, un transformation is faite par défaut.

    +

    Yesod fait de son mieux pour typer les objets manipulés et ainsi il fera ce qu’il faut pour ne pas mettre du script dans une URL par exemple.

    +

    Go to the other page ~~~~~~

    +

    Comme AnotherPageR est une URL elle ne pourra contiendra pas (par défaut) de caractère dangereux comme par exemple :

    +
    falselink"><script> bad_code(); </script><a href="pipo
    +

    Les widgets

    +

    Les widgets de yesod sont différents des widgets Javascripts (ou java). Pour yesod un widget est un ensemble de morceaux d’appli web. Et si dans une page on veut utiliser plusieurs widgets, alors yesod s’occupe de tout. Des exemples de widgets (au sens yesod) sont :

    +
      +
    • Le «footer» d’une page web,
    • +
    • Le «header» d’une page web,
    • +
    • un bouton qui apparaît lorsque l’on «scrolle» vers le bas,
    • +
    • etc…
    • +
    +

    Pour chacun de ces widgets vous pourriez avoir besoin d’

    +
      +
    • un peu d’HTML,
    • +
    • un peu de CSS et
    • +
    • un peu de javascript.
    • +
    +

    Certain morceau doivent être placés dans le «header» de la page et d’autre dans le «body».

    +

    Vous pouvez déclarer un widget comme suit (je n’utilise pas la vrai syntaxe) :

    +
    htmlheader = ...
    +cssheader = ...
    +javascriptheader = ...
    +htmlbody = ...
    +

    La vraie syntaxe est :

    +
    toWidgetHeader cassiusFile "button.cassius"
    +toWidgetHeader juliusFile "button.julius"
    +toWidget       hamletFile "buttonTemplate.hamlet"
    +

    Veuillez aussi noté la convention Shakespearienne des noms. Encore une bonne raison d’utiliser yesod.

    +
      +
    • Cassius & Lucius pour le CSS (très similaire à SASS et SCSS)
    • +
    • Julius pour le javascript (notons qu’il existe aussi un CoffeeScript qui traîne dans les sources de yesod)
    • +
    • Hamlet pour l’HTML (similaire à haml)
    • +
    +

    Lorsque vous générez votre page, yesod se débrouille pour que tout fonctionne ensemble:

    +
    myBigWidget =  menuWidget >> contentWidget >> footerWidget
    +

    De plus, si vous utilisez 10 widgets avec un peu de CSS, yesod fabriquera un unique fichier CSS pour vous. Bien entendu si vous préférez avoir une dizaine de fichier CSS vous pouvez aussi le faire.

    +

    C’est juste génial !

    +

    Routage optimisé

    +

    Dans un système de routage standard (à la ruby on rails par exemple) vous avez pour chaque entrée un couple: regexp → handler

    +

    La seule façon de découvrir la bonne règle est d’essayer de matcher l’url demandée à chaque expression régulière.

    +

    Au lieu d’essayer chaque expression régulière, yesod regroupe et compile les routes pour les optimiser. Bien entendu pour pouvoir profiter de cet avantage au mieux, il ne faut pas que deux routes interfèrent entres elles.

    +
    /blog/2003  Date2003R
    +/blog/$DATE DateR
    +

    Cette définition de route est invalide par défaut dans yesod. Si vous voulez vraiment vous pouvez le faire foncionner quand même, mais il me semble que ça doit être quasiment toujours une mauvaise idée.

    +

    Il vaut mieux faire :

    +
    /blog/$DATE DateR
    +

    et faire le test “est-ce que date = 2003 ?” dans le «handler».

    +

    Pourquoi yesod?

    +
      +
    1. La vitesse. Simplement incroyable, je ne pense pas qu’il existe quelque chose de plus rapide aujourd’hui. Regardez d’abord cet article puis celui-ci.
    2. +
    3. Haskell. C’est certainement le langage de programmation le plus difficile à apprendre que j’ai jamais rencontré. Mais aussi l’un des plus incroyables. Si vous voulez rencontrer tout un tas de notions que vous n’avez jamais croisées avant et faire exploser votre cerveau avec de nouvelles idées, alors apprenez Haskell.
    4. +
    5. Bonnes idées et communauté excellente. Cela fait quelques mois que je suis la progression de yesod. Et la vitesse à laquelle tout s’est déroulé est simplement incroyable. De plus les développeurs sont intelligents et super sympa.
    6. +
    +

    Si vous êtes un “haskeller”, je pense que vous ne devriez pas avoir peur de la syntaxe particulière imposée par la façon standard de faire les choses avec yesod. Il faut essayer un peu plus loin que les premiers tutoriaux du livre.

    +

    Je pense que yesod va dans la bonne direction d’un web plus sûr et plus rapide. Même si je pense que l’avenir sera que les serveurs devront être limités à faire serveur d’API (JSON ou XML ou n’importe quel autre mode de représentation d’objets).

    +

    Yesod est juste incroyable. Dépassez les difficultés liées à l’apprentissage d’haskell et essayez le !

    ]]>
    +
    + + Mon expérience avec les languages de programmation + + http://yannesposito.com/Scratch/fr/blog/programming-language-experience/index.html + 2011-09-28T00:00:00Z + 2011-09-28T00:00:00Z + Title image

    +
    +tlpl: Mon avis succinct et hautement subjectif concernant les différents languages de programmation que j’ai utilisé. +
    + +

    BASIC

    +

    Title image

    +

    Ah ! Le language de mes premiers programmes ! Je devais avoir 10-11 ans. Sous MO5, Amstrad CPC 6128 et même Atari STe. Le langage des GOTOs. Je suis empleint de nostalgie rien que d’y penser. C’est à peu prêt le seul intérêt de ce langage.

    +

    Aujourd’hui ce langage est tombé en désuétude. Ce n’est ni un bon langage pour apprendre, ni un bon langage pour faire de vrai programmes. Même si quelques années plus tard, je me remettais à programmer dans un basic avec un compilateur qui pourrait lui redonner vie. Je m’en était servi pour faire un livre dont vous êtes le héro :-).

    +
    READY
    +10 PRINT "HELLO WORLD!"
    +20 GOTO 10
    +RUN
    +

    Je m’en souviens aussi pour avoir copier des codes de jeux vidéo à partir de magasines. La plupart des lignes ressemblaient à

    +
    3110 DATA FA,01,FF,FF,FF,FF,00,23,22,43,DA,DE,EE,FF,FF,FF,00,03,4A,F2
    +

    Quel plaisir c’était !

    + +

    Dragon fractal

    +

    Toujours lors que j’avais 10 ans, on pouvait faire de petits programmes sympathiques.

    +

    Je me souviens que lors du chargement de l’application logo on avait droit à de la musique de Bach.

    +

    Oui, il fallait charger le programme en mémoire avec une cassette. Et elle ne faisait pas les ‘Krrrkrr csssss krrrr’.

    +

    Je l’avais utilisé sans les boucles. Des années plus tard, je le réutiliser pour faire de l’initiation à l’informatique à mes étudiants de DEUG MIAS première année. Il s’est en fait révélé très utile. Grace à lui, faire des fractales se révèle être un jeu d’enfant, au sens litéral. Je ne peux que conseiller ce langage pour apprendre à programmer et aussi pour le fun.

    +

    Voici un exemple de code et le résultat est la jolie fractale ‘dragon’.

    +
    HIDETURTLE
    +
    +PENUP
    +SETXY -200 0
    +RIGHT 90
    +PENDOWN
    +
    +to dragon :degree :size
    +    setpensize 1
    +    if :size>5  [setpensize 2]
    +    if :size>10 [setpensize 3]
    +    if :size>20 [setpensize 4]
    +    if :size>40 [setpensize 5]
    +    ifelse :degree=0 [
    +        fd :size
    +    ][
    +        left  45 dragon (:degree-1) (size/4)
    +        right 90 dragon (:degree-1) (size/2)
    +        left  90 dragon (:degree-1) (size/4)
    +        right 45
    +    ]
    +end
    +
    +dragon 6 3000
    +

    Pascal

    +

    L’éternel numéro 2.

    +

    J’ai dû apprendre à programmer en Pascal aux alentour de 15 ans et je l’ai aussi réutiliser un peit peu en faculté. Je dois avouer, que je le trouve inférieur au C en tous points. J’ai fait pas mal de chose avec ça, comme des algorithmes de graphes, des algorithmes de tri, et même un peu d’intelligence artificielle comme des algorithmes génétiques. Mais je préfère largement le C.

    +

    C

    +

    Pointer representation from Dancing links

    +

    Le langage des pointeurs

    +

    Ah, le langage de programmation par excellence.

    +

    Une fois que vous avez compris les boucles et la récursivité. Il est temps de passer aux choses sérieuses. Si vous voulez avoir du code de bonne qualité, alors apprendre le C est quasi-obligatoire.

    +

    Ce langage est très proche du langage machine. En particulier, (la majorité du temps). Il y a une relation linéaire entre la taille du code en C et de son résultat compilé en assembleur.

    +

    Ça signifie qu’à chaque fois que vous écrivez une ligne de C, il ne va pas se passer de choses toutes bizarres comme lancer un algorithme qui va prendre deux plombes.

    +

    Il est très proche de la machine tout en ayant une abstraction suffisante pour ne pas être “trop” désagréable.

    +

    J’ai fait beaucoup de choses avec. Tous les algorithmes de tri, des algorithmes d’intelligence artificielle (résolution de SAT3), du système, du réseau etc… Bref il est versatile, et on ne peut pas dire que l’on sait programmer si on ne s’est jamais mis à programmer sérieusement en C.

    +

    ADA

    +

    Le langage “super propre”.

    +

    J’avais bien aimé ADA, mais j’avoue que ça n’a duré que le temps d’un semestre de cours. Peut-être qu’un jour je m’y remettrai. Disons qu’il est assez vieux et qu’il a inspiré la plupart des concepts objets.

    +

    Les langages orientés objets

    +

    Bon, oui, le Pascal, le C, le Basic (fortran, Cobol et autres) étaient tous des langages impératifs, sans notion d’objets.

    +

    En gros, il n’y avait pas d’aide pour structurer votre code.

    +

    Alors, pour aider à limiter le nombre de bug, en particulier pour la création de très gros programmes, on s’est mis à réfléchir à la meilleure façon d’organiser du code d’ordinateur. À la fin, ça à donné la programmation orienté objet. Et donc les langages comme le C manquaient de système pour aider au développement orienté objet. Attention, la programmaiton orienté objet n’est pas la panacée. Combien de programme utilisez-vous qui n’ont pas de bug ? Et ça ne convient pas à tous les type de problème. Mais pour faire une application banquaire, un système de gestion des stocks, des clients ou des archives. C’est-à-dire un système d’information, c’est pas trop mal.

    +

    Donc les langages orientés objets se sont mis à fleurir.

    +

    C++

    +

    Messy router

    +

    Le malpropre

    +

    en:

    +

    Et oui l’industrie voulait un langage objet, mais elle n’était pas prête à mettre à la poubelle tout ses codes en C. La solution, prendre C et lui rajouter une couche objet. Le problème avec C++ c’est qu’il fait trop de choses. L’héritage multiple, des templates, etc… Bon, je l’ai quand même choisi pour faire le plus gros programme que j’ai jamais fais lors de ma thèse. Et je dois avouer que l’expérience m’a plu. Le seul reproche que j’ai à faire, c’est que la STL n’était pas aussi complète que l’on aurait pu l’espérer pour un détail. On ne peut pas faire de String<T> pour autre chose que des char16. Du coup, mon alphabet était limité à 216 lettres. Hors, pour certaines application, l’alphabet doit être gigantesque. fr: En conclusion je dirai que C++ est un très bon langage si vous vous fixez à l’avance un sous ensemble de ses fonctionnalités.

    +

    Eiffel

    +

    Eiffel tower construction

    +

    Eiffel est un très beau langage objet. Bien plus propre que C++. Mais, à moins que les choses aient changées, il n’est pas très populaire. Derrière lui il n’a pas la communauté de C++. Pour être franc, j’ai préféré travailler en C++. J’ai menti à mes profs de l’époque pour leur faire plaisir. Lorsqu’on viens du C, il est désagréable de changer ses habitudes.

    +

    Java

    +

    Holy Grail from the Monty Python

    +

    On continue vers les langages objets. Alors, à une époque où j’en ai entendu parler, c’était le Graal !

    +

    La portabilité, votre programme marchera partout. Il était orienté objet. Incrusté à l’intérieur il y avait des concepts d’architecture qui empêchent de faire n’importe quoi… Sauf que.

    +

    Sauf qu’il est incroyablement verbeux. Et que les limitations sont très désagréables si on sait ce que l’on fait.

    +

    Par exemple, il n’y a pas d’héritage multiple en Java. Ce qui est en général un choix que je trouve cohérent s’il est bien appuyé par des systèmes qui compensent ce manque. En java, il existe les interfaces. Les interfaces permettent d’ajouter des méthodes à une classe. En aucun cas on ne peut rajouter un attribut autrement qu’en héritant. Cet état de fait m’a vraiment géné.

    +

    Typiquement je faisais une GUI en Java Swing. J’avais créé mon propre système de notification entre objets. Au début je considérais qu’un objet ne devait envoyer des notifications qu’à un seul objet. Ô quelle erreur lorsque je réalisais qu’il fallait non plus gérer un seul objet mais parfois plusieurs. Je changeais mon implémentation d’interface partout, conséquence, des copier/coller dans tous les sens pour mes classes. Les copier/coller qui sont justement un problème censé être évité par les langages orientés objets.

    +

    De plus toujours pour ma GUI, je devais évidemment gérer des threads. Hors, il m’a fallu faire mon propre système de gestion de threads pour éviter les locks, pour les notifications (ce thread à fini, etc…). À l’époque j’utilisais Java 1.5. Normallement ce problème devait être réglé sur Java 1.6. J’espère que c’est le cas, mais avoir ce type de “feature” essentielle oubliée par le langage était assez grave.

    +

    De même, il a fallu attendre très longtemps avant d’avoir des boucles foreach qui rendent le code bien plus lisible.

    +

    Bon, après cette expérience je déconseillerai Java. La portabilité, n’est pas si intéressante que ce qu’on pourrait croire.

    +

    En ce qui concerne les GUI, portable signifie interface fonctionnelle mais médiocre sur toutes les plateformes. Quelque soit le système d’ailleurs (wxWidget, QT, etc…). Donc, pour des applications à distribuer à des tiers, c’est à éviter.

    +

    Le système de Java est très clos. Par contre il résout un très bon problème. Il permet à des développeurs médiocres de travailler en groupe sans faire trop de mal. Et un bon programmeur sera tout de même capable d’y faire des choses très intéressantes. Veuillez noter que je n’ai pas dit que les programmeurs Java sont de mauvais programmeurs, ce n’est pas ce que je pense.

    +

    Objective-C

    +

    Xcode Logo

    +

    Le langage que je n’ai appris et utilisé que pour faire des applications sur les plateformes d’Apple(c). J’ai appris Objective-C après Python. Et je dois avouer que j’ai eu du mal à m’y mettre. Je n’ai pas du tout aimé la syntaxe et pas mal d’autres détails. Mais ça fait parti de ces langages que plus on utilise, plus on aime. En réalité, il y a quelque chose dans ce langage qui fait que tout est bien pensé. Mais surtout, ici, ce n’est pas le langage qui est la meilleure partie, c’est plutôt le framework Cocoa qui lui est le plus souvent associé qui est une merveille. Par rapport à tous les autres framework permettant de fabriquer des GUI, Cocoa est de très loin supérieur. Même si ça semble être des détails sur le papier, en pratique cela fait une grande différence.

    +

    Vraiment jusqu’ici, même si Objective-C reste assez bas niveau, le fait que le typage de ce langage soit dynamique est un vrai plus pour l’interface graphique. Je ne peux que vous encourager à vous accrocher à ce langage et de faire un vrai programme avec. Vous en serez certainement plus ravi qu’il n’y parrait eu début.

    +

    Les langages interprétés modernes

    +

    PHP

    +

    A Jacky Touch Car

    +

    Le petit langage de script que nous utilisions tous pour faire des sites web à l’époque des gifs animées !

    +

    Sympatique, mais sans plus. Apparemment il y a eu pas mal de progrès depuis PHP5, un jour peut-être que j’y reviendrai. Mais, il a derrière lui une réputation de langage pour les “scripts kiddies”. En gros ceux qui ne savent pas coder. Des trous de sécurité de tous les cotés, etc…

    +

    En réalité, PHP est au niveau d’abstration à peine supérieur au C. Et donc, il est beaucoup moins bien organisé que des langages objets, favorisant ainsi la création de bug. Pour les applications web, c’est un vrai problème.

    +

    PHP, reste pour moi le langage de l’injection SQL. J’en fait encore un peu de temps en temps. Et j’ai moi-même dû protéger les accès au SQL pour éviter les injections. Oui, je n’ai pas trouvé de librairie toute prête pour protéger les entrées SQL. Je n’ai pas beaucoup cherché non plus.

    +

    Python

    +

    Python. Do you speak it?

    +

    Alors là, attention ! Révélation !

    +

    Lorsqu’on avait l’habitude de travailler avec des langages compilé, type C++, Java et qu’on passe à Python, on se prend une claque magistrale. La programmation comme elle doit être faite. Tout est si naturel, c’est magique. Oui, c’est si bien que ça. Mais quelque chose d’aussi incroyablement bien doit avoir des inconvénients me dirais-vous.

    +

    Et bien, oui, comme tous les langages de scripts de haut niveau, Python est lent. Attention pas juste un peu lent, comme 2 fois plus lent que du C. Non, de l’ordre de 10 à 20 fois plus lent que le C. Argh… Bon ça reste utilisable pour beaucoup de choses. Mais certaines application lui sont donc interdites.

    +

    Awk

    +

    Des filtres de fichiers à faire. Si ce n’est pas trop compliqué, c’est le langage idéal. Vous avez un fichier et vous voulez savoir quels sont les mots les plus utilisés. Savoir combien de fois un mot est utilisé. Filtrer sous des condition un peu plus compliquées qu’un grep. Super outils. Je l’ai utilisé pour modifier en masse des centaines de fichier XML plus facilement qu’avec du XSLT.

    +

    Perl

    +

    Perl c’est assez magique, mais la syntaxe est tellement désagréable à lire que personne ne peut vraiment aimer programmer dans un environnement de plusieurs personnes en Perl. A moins que tous les autres soient des cadors du Perl. Mais la feature qui tue, les expressions régulières :

    +
    $var =~ s/toto/titi/g
    +

    Va remplacer toto par titi dans la valeur de la variable $var. Et oui, les expressions régulière y sont intégrées directement comme avec sed et awk. Et ça rend le code beacoup plus compact (et parfois illisible). Mais c’est vraiment pas mal. C’est une sorte de awk sous stéroides.

    +

    Ruby

    +

    C’est une sorte de Perl en plus propre. Un mélange de Perl et de Python. Les notion objets y sont plus fortes qu’en Python. Je l’ai beaucoup utilisé, je reste quand même un Pythoniste de préférence. Mais Ruby est vraiment très bien. Par contre en terme d’efficacité, c’est le pire langage utilisé par beaucoup de monde de ce point de vue. C’est le langage qui perd quasiment tous les benchmarks. Par contre c’est un outil parfait pour faire des prototypes. Et si vous voulez faire un prototype de site web, RoR est ce qui se fait de mieux. De l’idée au site, il ne se passera que peu de temps.

    +

    Javascript

    +

    C’est la bonne surprise. Pendant des années, javascript était considéré comme un langage tout bon à vous embéter dans votre navigation web. En réalité, javascript possède beaucoup de qualité des langages de haut niveau. En particulier, il est facille de passer une fonction en paramèter ou de créer des fonctions anonymes (closures). Récemment, il est devenu très rapide et beaucoup de frameworks et de librairies naissent un peu partout.

    +
      +
    • Il y a Cappuccino, Objective-J (comme de l’objective-C mais avec du javascript)
    • +
    • Sproutcore
    • +
    • Spine.js
    • +
    • Backbone.js
    • +
    • jQuery
    • +
    • prototype.js
    • +
    +

    En particulier avec jQuery, on peut faire des appels chainés, très agréables à utiliser. Comme je le disais, c’est une bonne surprise, javascript a été choisi un peu au hasard lors de la création des navigateurs web comme langage de script. Et il s’avère qu’à part sa syntaxe, tout le reste est bien. Heureusement, en ce qui concerne la syntaxe, on peu pallier à ce problème en utilisant CoffeeScript.

    +

    Les langages fonctionnels

    +

    CamL

    +

    J’ai appris CamL à la fac, j’avais trouvé cette expérience très interressante. J’étais plutôt bon, et j’avais les bonnes intuitions mathématiques qui vont avec la programmation fonctionnelle. Mais je dois avouer que je ne l’ai plus jamais utilisé. Simplement, ce type de langage semble si loin de ce qui se fait pour fabriquer des produits que ça me donnais vraiment l’impression d’être un langage pour chercheurs.

    +

    Haskell

    +

    Je suis en train d’apprendre ce langage. Et je dois dire que c’est un vrai plaisir. En général les concepts derrière tous les langages de programmation sont assez limités. Chaque langage y va de son petit lot de nouveau concepts, et en général en une après-midi, c’est appris. Pour haskell, c’est très différent. Je sens bien qu’il va me falloir plusieurs semaines pour maîtriser la bête. Ça doit faire quatre semaines que j’apprend haskell un peut tous les jours et je sais qu’il y a des notions que j’ai juste survollées et qui sont assez incroyables. Les Monades par exemple, est un concept que je n’avais jamais rencontré ailleurs. C’est un super concept. De plus le design du langage en fait un parfait système pour paralléliser les calculs naturellement. haskell sépare la partie “pure” de la partie “impure” de la programmation. À ma connaissance, c’est le seul langage de programmation qui fait ça. Enfin, je prend beaucoup de plaisir à apprendre ce langage. La communauté est aussi très acceuillante. Pas de “L0L! URAN00B!”. Et aussi pas de concession du langage pour devenir populaire. Le langage est bon, voilà tout. Alors qu’en Java et C++, typiquement certain choix ont été fait en dépis du bon sens pour “faire plaisir”.

    +

    Langages originaux

    +

    Metapost

    +

    Metapost est un langage qui permet de programmer des dessins. Le gros plus de metapost, c’est sa capacité de résoudre automatiquement les systèmes d’équations linéaires. Par exemple, si vous écrivez :

    +
    AA=1/3[A,B]
    +

    Il va position le point AA entre A et B. Plus précisément, au barycentre (2A + B)/3.

    +
    X=whatever[A,B]
    +X=whatever[C,D]
    +

    Ce deuxième exemple positionne X à l’intersection des deux segments AB et CD. Vous pouvez aussi voir pas mal d’exemples ici. You could see more example there.

    +

    Cette fonction est très utile. Et à mon avis pas seulement pour afficher des choses. De mon point de vue, les autres langages de programmation devraient penser à rajouter les résolutions automatiques simples.

    +

    zsh

    +

    Oui, zsh est un shell. Mais c’est aussi un langage de script très bien adapté aux traitement de fichiers. Je le recommande chaudement. C’est pour l’instant le meilleur shell que j’ai utilisé. Je le préfère au bash.

    +

    Prolog

    +

    Je n’ai jamais rien fait de conséquent avec Prolog, mais j’ai adoré l’apprendre et l’utiliser. J’ai eu la chance d’apprendre Prolog par Alain Colmerauer lui-même. C’est un langage qui essaye de résoudre les contraintes autant qu’il le peut pour vous. Il en ressort un impression de magie. On ne fait que décrire ce qu’il faut et on ne donne pas d’ordre. Un peu comme la programmation fonctionnelle mais en beaucoup plus puissant.

    +

    Les langages à découvrir

    +

    Il reste encore pas mal de langages et de framework à essayer. Actuellement je pense que je vais passer un moment avec Haskell. Peut-être demain que j’irai apprendre LISP, Scala ou Erlang. Comme je suis plus dans la création de site web, j’irai certainement jeter un coup d’œil à clojure aussi. Et certainement beaucoup d’autres choses.

    +

    Dites moi si vous avez une autre expérience avec ces langages de programmation. Évidement mes impression sont hautement subjectives. Cependant, j’ai utilisé tous les langages dont j’ai parlé.

    +

    [STL]: Standard Tempate Library[GUI]: Graphic User Interface

    ]]>
    +
    + + Fonctions d'ordre supérieur en zsh + + http://yannesposito.com/Scratch/fr/blog/Higher-order-function-in-zsh/index.html + 2011-09-28T00:00:00Z + 2011-09-28T00:00:00Z + Title image

    +
    + +

    UPDATE: Nicholas Sterling a découvert un moyen de faire des fonctions anonymes Merci!

    +

    Avec cette dernière version vous pouvez utiliser map si vous utilisez des fonctions déclarées. mapl pour les fonctions anonymes et mapa pour les fonctions arithmétiques.

    +

    Exemple :

    +
    $ filterl 'echo $1|grep a >/dev/null' ab cd ef ada
    +ab
    +ada
    +
    +$ folda '$1+$2' {1..5}
    +15
    +
    +$ folda '$1*$2' {1..20}
    +2432902008176640000
    +
    +$ mapl 'echo X $1:t Y' ~/.zsh/functional/src/*
    +X each Y
    +X filter Y
    +X fold Y
    +X map Y
    +
    +$ mapa '$1*2' {1..3}
    +2
    +4
    +6
    +
    +$ mapl 'echo result $1' $(mapa '$1+5' $(mapa '$1*2' {1..3}))
    +result 7
    +result 9
    +result 11
    +
    +

    tlpl: des fonctions d’ordres supérieurs en zsh.

    +
    + +

    Tout d’abord, pourquoi c’est important d’avoir ces fonctions. Plus je programmais avec zsh plus j’essayais d’avoir un style fonctionnel.

    +

    Le minimum pour pouvoir avoir du code plus lisible c’est de posséder les fonctions map, filter et fold.

    +

    Voici pourquoi avec une comparaison. Commençons par un programme qui converti tous les gif en png dans plusieurs répertoires projets contenant tous des répertoires resources. Avant :

    +

    Avant ⇒

    +
    # for each directory in projects dir
    +for toProject in /path/to/projects/*(/N); do
    +    # toProject is /path/to/projects/foo
    +    # project become foo (:t for tail)
    +    project=${toProject:t}
    +    for toResource in $toProject/resources/*.gif(.N); do
    +        convert $toResource ${toResource:r}.png && \
    +        \rm -f $toResource
    +    done
    +done
    +
      +
    • Le (/N) permet de sélectionner seulement les répertoires sans casser la boucle s’il n’y a pas de “match”.
    • +
    • Le (.N) permet de sélection seulement les fichiers, aussi sans tout arréter s’il ne trouve rien.
    • +
    • Le :t signfie “tail” ; si toto=/path/to/file.ext alors ${toto:t}=file.ext.
    • +
    +

    Après

    +
    gif_to_png() { convert $1 ${1:r}.png && \rm -f $1 }
    +
    +handle_resources() { map gif_to_png $1/resources/*.gif(.N) }
    +
    +map handle_resources /path/to/projects/*(/N)
    +

    Plus de bloc ! Oui, c’est un poil plus difficile à lire pour les non initiés. Mais c’est à la fois plus concis et plus robuste.

    +

    Et encore ce code ne possède pas de test. Recommençons sur le même principe.

    +

    Trouver les fichiers des projets qui ne contiennent pas de s dans leur nom qui ont le même nom que leur projet.

    +

    Before ⇒

    +
    for toProject in Projects/*; do
    +    project=$toProject:t
    +    if print -- project | grep -v s >/dev/null
    +    then
    +        print $project
    +        for toResource in $toProject/*(.N); do
    +            if print -- ${toResource:t} | grep $project >/dev/null; then
    +                print -- "X $toResource"
    +            fi
    +        done
    +    fi
    +done
    +

    After ⇒

    +
    contain_no_s() { print $1 | grep -v s }
    +
    +function verify_file_name {                               
    +    local project=$1:t
    +    contains_project_name() { print $1:t | grep $project }
    +    map "print -- X" $(filter contains_project_name $1/*(.N))
    +}
    +
    +map verify_file_name $( filter contain_no_s Projects/* )
    +

    La première version peu paraître plus facile à lire. Mais la seconde est plus bien supérieure en terme d’architecture. Je ne veux pas discuster ici pourquoi c’est mieux. Je vous demande simplement de me croire quand je dis que l’approche fonctionnelle est supérieure.

    +

    Vous pouvez télécharger une version à jour du code (merci à Arash Rouhani). Une ancienne version est ici. Voici le code source (de la première version) :

    +
    #!/usr/bin/env zsh
    +
    +# Provide higer-order functions 
    +
    +# usage:
    +#
    +# $ foo(){print "x: $1"}
    +# $ map foo a b c d
    +# x: a
    +# x: b
    +# x: c
    +# x: d
    +function map {
    +    local func_name=$1
    +    shift
    +    for elem in $@; print -- $(eval $func_name $elem)
    +}
    +
    +# $ bar() { print $(($1 + $2)) }
    +# $ fold bar 0 1 2 3 4 5
    +# 15
    +# -- but also
    +# $ fold bar 0 $( seq 1 100 )
    +function fold {
    +    if (($#<2)) {
    +        print -- "ERROR fold use at least 2 arguments" >&2
    +        return 1
    +    }
    +    if (($#<3)) {
    +        print -- $2
    +        return 0
    +    } else {
    +        local acc
    +        local right
    +        local func_name=$1
    +        local init_value=$2
    +        local first_value=$3
    +        shift 3
    +        right=$( fold $func_name $init_value $@ )
    +        acc=$( eval "$func_name $first_value $right" )
    +        print -- $acc
    +        return 0
    +    }
    +}
    +
    +# usage:
    +#
    +# $ baz() { print $1 | grep baz }
    +# $ filter baz titi bazaar biz
    +# bazaar
    +function filter {
    +    local predicate=$1
    +    local result
    +    typeset -a result
    +    shift
    +    for elem in $@; do
    +        if eval $predicate $elem >/dev/null; then
    +            result=( $result $elem )
    +        fi
    +    done
    +    print $result
    +}
    ]]>
    +
    + + Apprenez Vim Progressivement + + http://yannesposito.com/Scratch/fr/blog/Learn-Vim-Progressively/index.html + 2011-08-25T00:00:00Z + 2011-08-25T00:00:00Z + Über leet use vim!

    +
    + +

    tlpl: Vous désirez apprendre vim (le meilleur editeur de texte connu à ce jour) le plus rapidement possible. Voici mes conseils pour vous aider. Commencez à apprendre le minimum vital, puis apprenez doucement de nouvelles commandes.

    +
    + +

    Vim ou l’éditeur qui vallait 3 milliards :

    +
    +

    Meilleur, plus fort, plus rapide.

    +
    +

    Apprenez vim et ce sera votre dernier éditeur. Aucun éditeur que je connaisse ne le surpasse. Sa prise en mais est difficile, mais payante.

    +

    Je vous conseille de l’apprendre en 4 étapes :

    +
      +
    1. La survie
    2. +
    3. Se sentir à son aise
    4. +
    5. Se sentir meilleur, plus fort et plus rapide
    6. +
    7. Tirer parti des super-pouvoirs de vim
    8. +
    +

    À la fin de ces leçons vous serez transformé.

    +

    Avant de commencer, un message pour vous prévenir. Apprendre vim sera difficile au début. Ça prendra du temps. Vous devrez vous entraîner. Apprendre vim ressemble beaucoup à apprendre un instrument de musique. N’espérez pas être plus efficace avec vim qu’avec un autre éditeur avant au moins trois jours. En fait ça sera certainement plus 2 semaines que 3 jours.

    +

    1er Niveau – Survivre

    +
      +
    1. Installez vim
    2. +
    3. Lancez vim
    4. +
    5. NE TOUCHEZ A RIEN! Lisez
    6. +
    +

    Dans un éditeur normal, il suffit de taper sur une touche du clavier et la lettre s’affiche à l’écran. Pas ici. Vim est en mode Normal. Commençons par placer vim en mode Insert. Tapez sur la touche i.

    +

    Voilà, c’est magique. Vous pouvez tapez comme dans un éditeur standard. Pour repasser en mode Normal tapez sur la touche Echap.

    +

    Maintenant que vous savez passer du mode Normal au mode Insert. Voici les commandes de survie (toutes en mode Normal) :

    +
    +
      +
    • i → Passer en mode insértion. Taper Echap pour repasser en mode Normal.
    • +
    • x → Supprimer le caractère sous le curseur
    • +
    • :wq → Sauvegarder et quitter (:w sauvegarde, :q<enter> quitter)
    • +
    • dd → Supprimer (et copier) la ligne courante
    • +
    • p → Coller
    • +
    +

    Récommandées :

    +
      +
    • hjkl (optionnel) → se déplacer (<-↓↑→). Souvenez vous j ressemble à une flèche vers le bas.
    • +
    • :help <commande> → Affiche l’aide pour <commande>. Vous pouvez aussi écrire :help pour atterir sur l’aide générale.
    • +
    +
    +

    Seulement 5 commandes. Voilà, c’est tout pour un début. Essayez d’éditer vos fichiers comme ça pendant une petite journée. Lorsque ces commandes vous sembleront naturelles, vous pourrez passer à l’étape d’après.

    +

    Mais avant un petit mot sur le mode Normal. Dans un éditeur normal pour copier il faut utiliser une combinaison de touches (Ctrl-c). En fait, lorsque vous appuyez sur la touche Ctrl, c’est un peu comme si toutes les touches du clavier avaient un autre usage. Dans vim, lorsque vous êtes en mode Normal, c’est comme si vous mainteniez Ctrl enfoncé.

    +

    Quelques mots concernant les notations :

    +
      +
    • Au lieu d’écrire Ctrl-λ, j’écrirai <C-λ>.
    • +
    • Les commandes qui commencent par : ont un retour à la ligne implicite à la fin. Par exemple lorsque que j’écris, :q celà signifi qu’il faut taper :, suivi de q, suivi de <Return>.
    • +
    +

    2ème Niveau – Se sentir à son aise

    +

    Vous connaissez les commandes de survie. Passons à des commandes pour être un peu plus à notre aise. Je vous suggère :

    +
      +
    1. Les variantes de l’insertion

      +
      +
        +
      • a → Comme i, mais après la position du curseur.
      • +
      • o → Comme i, mais à la ligne suivante.
      • +
      • O → Comme o mais ajoute la ligne avant.
      • +
      • cw → Remplacer la fin du mot.
      • +
      +
    2. +
    3. Déplacements basiques

      +
      +
        +
      • 0 → Aller à la première colonne.
      • +
      • ^ → Aller au premier caractère de la ligne.
      • +
      • $ → Aller à la fin de la ligne.
      • +
      • g_ → Aller au dernier caractère de la ligne.
      • +
      • /pattern → Rechercher pattern dans le fichier.
      • +
      +
    4. +
    5. Copier/Coller

      +
      +
        +
      • P → Coller avant. Souvenez vous, p colle après la position du curseur.
      • +
      • yy → Copier la ligne courante. C’est plus simple et équivalent à ddP
      • +
      +
    6. +
    7. Annuler/Refaire

      +
      +
        +
      • u → Annuler (undo)
      • +
      • <C-r> → Refaire
      • +
      +
    8. +
    9. Ouvrir/Sauvegarder/Quitter/Changer de fichier (buffer)

      +
      +
        +
      • :e <path/to/file> → Ouvrir.
      • +
      • :w → Sauvegarder.
      • +
      • :saveas <path/to/file> → Sauvegarder sous …
      • +
      • :x, ZZ ou :wq → Sauvegarder et quitter (:x sauvegarde seulement si nécessaire).
      • +
      • :q! → Quitter sans sauvegarder. De même :qa! quitte même si d’autres fichiers (buffers) ont des modifications non sauvegardées.
      • +
      • :bn (resp. :bp) → Affiche le fichier suivant (resp. précédent).
      • +
      +
    10. +
    +

    Prenez le temps de bien intégrer ces commandes. Une fois fait, vous devriez être capable de faire tout ce qu’on peut attendre d’un éditeur de texte classique.

    +

    3ième Niveau – Meilleur. Plus fort. Plus rapide.

    +

    Bravo ! Si vous êtes arrivé jusqu’ici nous allons pouvoir commencer à apprendre les choses vraiment intéressantes. Pour cette section, je vais seulement parler de commandes disponible dans vi et vim. Vim est la contraction de “vi improved”, ou en Français, “vi amélioré”.

    +

    Meilleur

    +

    Voyons comment nous pouvons éviter les répétitions avec vi :

    +
      +
    1. . → Le caractère point répètera la dernière commande. Très utile.
    2. +
    3. N<commande> → répètera la commande N fois.
    4. +
    +

    Quelques exemples, ouvrez un fichier (non vide) avec vim et tapez :

    +
    +
      +
    • 2dd → Supprimera 2 lignes
    • +
    • 3p → copiera 3 fois d’affiler le texte copié
    • +
    • 100idesu [ESC] → écrira “desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu”
    • +
    • . → Juste après la dernière commande réécrira les 100 “desu”.
    • +
    • 3. → Écrira 3 “desu” et non pas 300. Bien vu n’est-ce pas ?
    • +
    +
    +

    Plus fort

    +

    Savoir se déplacer efficacement avec vim est très important. Ne sautez pas cette section.

    +
      +
    1. NG → Aller à la ligne N
    2. +
    3. gg → raccourci pour 1G, retourner au début du fichier
    4. +
    5. G → Aller à la dernière ligne.
    6. +
    7. Déplacement autour des mots:

      +
      +
        +
      1. w → aller au début du mot suivant
      2. +
      3. e → aller à la fin du mot courant
      4. +
      +

      Par défaut les mots sont seulement composés de lettres (et du caractère souligné _). Appelons un MOT un ensemble de lettre séparé par des caractères blancs (espaces, tabulation). Si vous voulez considérer des MOTS alors il suffit d’utiliser les majuscules.

      +
        +
      1. W → aller au début du MOT suivant
      2. +
      3. E → aller à la fin du MOT courant
      4. +
      +

      Word moves example

      +
    8. +
    +

    Passons aux commandes de déplacement les plus efficaces :

    +
    +
      +
    • % : Aller à la parenthèse, accolade, crochet correspondante.
    • +
    • * (resp. #) : Aller à la prochaine (resp. précédente) occurrence du mot sous le curseur
    • +
    +
    +

    Croyez moi, ces trois dernières commandes valent de l’or. Retenez les et vous gagnerez beaucoup de temps.

    +

    Plus rapide

    +

    Vous vous souvenez que j’ai dit que les déplacements étaient très importants en vi. Voilà pourquoi. Une façon de travailler avec vim est de se dire que l’on fait des “phrases”. Le verbe étant la commande et les compléments définissent la zone d’action. De façon générale :

    +

    <position de depart><commande><position d'arrivee>

    +

    Par exemple : 0y$ signifie :

    +
      +
    • 0 → Aller au début de la ligne,
    • +
    • y → copie à partir d’ici,
    • +
    • $ → jusqu’à la fin de cette ligne.
    • +
    +

    On peut donc faire des choses comme ye, copie à partir de la position courante du curseur jusqu’à là fin du mot. Mais aussi: y2/toto copie jusqu’à la seconde prochaine occurrence de “toto”.

    +

    Ce qui est vrai pour y (yank → copier), est aussi vrai pour d (delete → supprimer), v (sélection visuelle), gU (uppercase → majuscule),gu (lowercase → minuscule), etc…

    +

    4ième Niveau – Les super pouvoirs de Vim

    +

    Jusqu’ici vous avez appris les commandes les plus courantes. Mais voici les killer features de vim. Celles que je n’ai retrouvé que dans vim (ou presque).

    +

    Déplacement sur la ligne : 0 ^ $ g_ f F t T , ;

    +
    +
      +
    • 0 → aller à la colonne 0,
    • +
    • ^ → aller au premier caractère de la ligne
    • +
    • $ → aller à la dernière colonne de la ligne
    • +
    • g_ → aller au dernier caractère de la ligne
    • +
    • fa → vous amène à la prochaine occurrence de a sur la ligne courante. , (resp. ;) recherche l’occurrence suivante (resp. précédente).
    • +
    • t, → vous amène juste avant le ,.
    • +
    • 3fa → recherche la 3ième occurrence de a.
    • +
    • F et T → comme f et t mais en arrière. Line moves
    • +
    +
    +

    Un truc pratique : dt" → supprime tout jusqu’au prochain ".

    +

    Selection de zone <action>a<object> ou <action>i<object>

    +

    Ces commandes sont utilisable seulement en mode visuel ou après un “opérateur”. Mais elles sont très puissantes. Leur forme générale est:

    +

    <action>a<objet> et <action>i<objet>

    +

    Où action peut être par exemple d (delete), y (yank), v (select in visual mode), etc… Un objet peut être: w un mot, W un MOT (mot étendu), s une phrase, p un paragraphe. Mais aussi des caractère plus naturels comme ", ', ), }, ].

    +

    Supposons que le curseur soit positionné sur le premier o dans (map (+) ("foo")).

    +
    +
      +
    • vi" → sélectionnera foo.
    • +
    • va" → sélectionnera "foo".
    • +
    • vi) → sélectionnera "foo".
    • +
    • va) → sélectionnera ("foo").
    • +
    • v2i) → sélectionnera map (+) ("foo")
    • +
    • v2a) → sélectionnera (map (+) ("foo"))
    • +
    +
    +

    Text objects selection

    +

    Sélection de blocs rectangulaires : <C-V>.

    +

    Les blocs rectangulaires sont très commodes pour commenter plusieurs lignes de codes. Typiquement: ^<C-V><C-d>I-- [ESC]

    +
      +
    • ^ → aller au premier caractère de la ligne
    • +
    • <C-V> → Commencer la sélection du bloc
    • +
    • <C-d> → se déplacer vers le bas (pourrait être jjj ou % etc…)
    • +
    • I-- [ESC] → écrit -- pour commenter le reste de la ligne.
    • +
    +

    Rectangular blocks

    +

    Remarquez que sous windows, vous devez utiliser <C-q> plutôt que <C-v> si votre “presse papier” n’est pas vide.

    +

    Complétion : <C-n> et <C-p>.

    +

    En mode Insert, commencez à écrire le début d’un mot déjà présent dans l’un des buffers (fichers) ouvert et tapes <C-p>. Magique. Completion

    +

    Macros : qa faire quelque chose q, @a, @@

    +

    qa enregistre tout ce que vous faite et enregistre le tout dans le registre a. Ensuite @a va rejouer la macro enregistrée dans le registre a comme si c’est vous qui tapiez au clavier. @@ est un raccourci pour rejouer la dernière macro exécutée.

    +
    +

    Exemple : Sur une ligne contenant seulement un 1 tapez :

    +
      +
    • qaYp<C-a>q

    • +
    • qa → début de l’enregistrement.
    • +
    • Yp → copier cette ligne.
    • +
    • <C-a> → incrémente le nombre.
    • +
    • q → arrête d’enregistrer.

    • +
    • @a → écrit un 2 sous le 1.
    • +
    • Écrivez 100@@. Cela va créer une liste de nombre croissants jusqu’à 103.

    • +
    +
    +

    Macros

    +

    Sélection visuelle : v,V,<C-v>

    +

    On a déjà vu un exemple avec <C-V>. Mais il y a aussi, v et V. Et une fois la sélection visuelle faite vous pouvez par exemple:

    +
      +
    • J → joindre toutes les lignes pour en faire une seule
    • +
    • < (resp. >) → indenter à gauche (resp. à droite).
    • +
    • = → auto indenter
    • +
    +

    Autoindent

    +

    Ajouter quelque chose à la fin de toutes les lignes sélectionnées visuellement :

    +
      +
    • <C-v>
    • +
    • aller jusqu’à la ligne désirée (jjj ou <C-d> ou /pattern ou % etc…)
    • +
    • $ aller à la fin
    • +
    • A, écrire le texte, Echap.
    • +
    +

    Ajouter à la fin de plusieurs lignes

    +

    Splits : :split et vsplit.

    +

    Je vous conseille de faire un :help split. Celà permet de manipuler plusieurs buffer sur la même fenêtre. Voici les commandes principales :

    +
    +
      +
    • :split → crée un split (:vsplit crée un split vertical)
    • +
    • <C-w><dir> → où dir est l’un de hjkl ou ←↓↑→ permet de changer de split.
    • +
    • <C-w>_ (resp. <C-w>|) → Maximise la taille du split (resp. split vertical)
    • +
    • <C-w>+ (resp. <C-w>-) → Agrandi (resp. diminue) le split
    • +
    +
    +

    Split

    +

    Conclusion

    +

    Voilà, je vous ai donné 90% des commandes que j’utilise tous les jours. N’essayez pas de toutes les apprendre en une journée. Il faut le temps de s’habituer à chaque nouvelle commande. Je vous conseille de ne pas apprendre plus d’une ou deux commandes par jour.

    +

    Apprendre Vim est plus une question d’entraînement que de mémorisation. Heureusement vim est founi avec un très bon tutoriel et une excellente documentation. Lancez vimtutor jusqu’à ce que vous vous sentiez à l’aise avec les commandes basiques. De plus, vous devriez aussi lire en détail la page suivate : :help usr_02.txt.

    +

    Ensuite vous découvrirez !, les folds, les registres, les plugins et tout un tas d’autres choses. Apprenez vim comme vous apprendriez le piano et vous devriez très bien vous en sortir.

    + + +]]>
    +
    + + diff --git a/Scratch/fr/blog/index.html b/Scratch/fr/blog/index.html new file mode 100644 index 0000000..9ac305c --- /dev/null +++ b/Scratch/fr/blog/index.html @@ -0,0 +1,343 @@ + + + + + + YBlog - Blog + + + + + + + + + + + + +
    + + +
    +

    Blog

    +
    +
    +
    +
    +

    Articles populaires

    + + + + + + +
    + +

    Archive

    + + + +
    + +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/mvc/index.html b/Scratch/fr/blog/mvc/index.html new file mode 100644 index 0000000..f3c2302 --- /dev/null +++ b/Scratch/fr/blog/mvc/index.html @@ -0,0 +1,137 @@ + + + + + + YBlog - Les raisons du MVC + + + + + + + + + + + + +
    + + +
    +

    Les raisons du MVC

    +
    +
    +
    +
    +

    Why This article and for whom?

    +

    Many website explaining how MVC works. But I can’t found one who explain why.

    +

    I have difficulties to obey some principle don’t know why I should use it. And something better than the:

    +
    +

    “Smarter people than you decided you have to do so”

    +
    +

    This article is for people who like me want to understand the real motivation of using an MVC architecture and which advantage it gives you.

    +

    Let’s start making a project from scratch pretending we don’t know anything about the MVC architecture. Then let see how we’ll naturally derive to an MVC architecture.

    +
    +

    +<%= lnkto(‘→ Next’,‘/blog/mvc/mvc1’) %> +

    +
    + + +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2009-07-06 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/blog/programming-language-experience/index.html b/Scratch/fr/blog/programming-language-experience/index.html new file mode 100644 index 0000000..cf01ea0 --- /dev/null +++ b/Scratch/fr/blog/programming-language-experience/index.html @@ -0,0 +1,267 @@ + + + + + + YBlog - Mon expérience avec les languages de programmation + + + + + + + + + + + + + +
    + + +
    +

    Mon expérience avec les languages de programmation

    +
    +
    +
    +
    +

    Title image

    +
    +tlpl: Mon avis succinct et hautement subjectif concernant les différents languages de programmation que j’ai utilisé. +
    + +

    BASIC

    +

    Title image

    +

    Ah ! Le language de mes premiers programmes ! Je devais avoir 10-11 ans. Sous MO5, Amstrad CPC 6128 et même Atari STe. Le langage des GOTOs. Je suis empleint de nostalgie rien que d’y penser. C’est à peu prêt le seul intérêt de ce langage.

    +

    Aujourd’hui ce langage est tombé en désuétude. Ce n’est ni un bon langage pour apprendre, ni un bon langage pour faire de vrai programmes. Même si quelques années plus tard, je me remettais à programmer dans un basic avec un compilateur qui pourrait lui redonner vie. Je m’en était servi pour faire un livre dont vous êtes le héro :-).

    +
    READY
    +10 PRINT "HELLO WORLD!"
    +20 GOTO 10
    +RUN
    +

    Je m’en souviens aussi pour avoir copier des codes de jeux vidéo à partir de magasines. La plupart des lignes ressemblaient à

    +
    3110 DATA FA,01,FF,FF,FF,FF,00,23,22,43,DA,DE,EE,FF,FF,FF,00,03,4A,F2
    +

    Quel plaisir c’était !

    + +

    Dragon fractal

    +

    Toujours lors que j’avais 10 ans, on pouvait faire de petits programmes sympathiques.

    +

    Je me souviens que lors du chargement de l’application logo on avait droit à de la musique de Bach.

    +

    Oui, il fallait charger le programme en mémoire avec une cassette. Et elle ne faisait pas les ‘Krrrkrr csssss krrrr’.

    +

    Je l’avais utilisé sans les boucles. Des années plus tard, je le réutiliser pour faire de l’initiation à l’informatique à mes étudiants de DEUG MIAS première année. Il s’est en fait révélé très utile. Grace à lui, faire des fractales se révèle être un jeu d’enfant, au sens litéral. Je ne peux que conseiller ce langage pour apprendre à programmer et aussi pour le fun.

    +

    Voici un exemple de code et le résultat est la jolie fractale ‘dragon’.

    +
    HIDETURTLE
    +
    +PENUP
    +SETXY -200 0
    +RIGHT 90
    +PENDOWN
    +
    +to dragon :degree :size
    +    setpensize 1
    +    if :size>5  [setpensize 2]
    +    if :size>10 [setpensize 3]
    +    if :size>20 [setpensize 4]
    +    if :size>40 [setpensize 5]
    +    ifelse :degree=0 [
    +        fd :size
    +    ][
    +        left  45 dragon (:degree-1) (size/4)
    +        right 90 dragon (:degree-1) (size/2)
    +        left  90 dragon (:degree-1) (size/4)
    +        right 45
    +    ]
    +end
    +
    +dragon 6 3000
    +

    Pascal

    +

    L’éternel numéro 2.

    +

    J’ai dû apprendre à programmer en Pascal aux alentour de 15 ans et je l’ai aussi réutiliser un peit peu en faculté. Je dois avouer, que je le trouve inférieur au C en tous points. J’ai fait pas mal de chose avec ça, comme des algorithmes de graphes, des algorithmes de tri, et même un peu d’intelligence artificielle comme des algorithmes génétiques. Mais je préfère largement le C.

    +

    C

    +

    Pointer representation from Dancing links

    +

    Le langage des pointeurs

    +

    Ah, le langage de programmation par excellence.

    +

    Une fois que vous avez compris les boucles et la récursivité. Il est temps de passer aux choses sérieuses. Si vous voulez avoir du code de bonne qualité, alors apprendre le C est quasi-obligatoire.

    +

    Ce langage est très proche du langage machine. En particulier, (la majorité du temps). Il y a une relation linéaire entre la taille du code en C et de son résultat compilé en assembleur.

    +

    Ça signifie qu’à chaque fois que vous écrivez une ligne de C, il ne va pas se passer de choses toutes bizarres comme lancer un algorithme qui va prendre deux plombes.

    +

    Il est très proche de la machine tout en ayant une abstraction suffisante pour ne pas être “trop” désagréable.

    +

    J’ai fait beaucoup de choses avec. Tous les algorithmes de tri, des algorithmes d’intelligence artificielle (résolution de SAT3), du système, du réseau etc… Bref il est versatile, et on ne peut pas dire que l’on sait programmer si on ne s’est jamais mis à programmer sérieusement en C.

    +

    ADA

    +

    Le langage “super propre”.

    +

    J’avais bien aimé ADA, mais j’avoue que ça n’a duré que le temps d’un semestre de cours. Peut-être qu’un jour je m’y remettrai. Disons qu’il est assez vieux et qu’il a inspiré la plupart des concepts objets.

    +

    Les langages orientés objets

    +

    Bon, oui, le Pascal, le C, le Basic (fortran, Cobol et autres) étaient tous des langages impératifs, sans notion d’objets.

    +

    En gros, il n’y avait pas d’aide pour structurer votre code.

    +

    Alors, pour aider à limiter le nombre de bug, en particulier pour la création de très gros programmes, on s’est mis à réfléchir à la meilleure façon d’organiser du code d’ordinateur. À la fin, ça à donné la programmation orienté objet. Et donc les langages comme le C manquaient de système pour aider au développement orienté objet. Attention, la programmaiton orienté objet n’est pas la panacée. Combien de programme utilisez-vous qui n’ont pas de bug ? Et ça ne convient pas à tous les type de problème. Mais pour faire une application banquaire, un système de gestion des stocks, des clients ou des archives. C’est-à-dire un système d’information, c’est pas trop mal.

    +

    Donc les langages orientés objets se sont mis à fleurir.

    +

    C++

    +

    Messy router

    +

    Le malpropre

    +

    en:

    +

    Et oui l’industrie voulait un langage objet, mais elle n’était pas prête à mettre à la poubelle tout ses codes en C. La solution, prendre C et lui rajouter une couche objet. Le problème avec C++ c’est qu’il fait trop de choses. L’héritage multiple, des templates, etc… Bon, je l’ai quand même choisi pour faire le plus gros programme que j’ai jamais fais lors de ma thèse. Et je dois avouer que l’expérience m’a plu. Le seul reproche que j’ai à faire, c’est que la STL n’était pas aussi complète que l’on aurait pu l’espérer pour un détail. On ne peut pas faire de String<T> pour autre chose que des char16. Du coup, mon alphabet était limité à 216 lettres. Hors, pour certaines application, l’alphabet doit être gigantesque. fr: En conclusion je dirai que C++ est un très bon langage si vous vous fixez à l’avance un sous ensemble de ses fonctionnalités.

    +

    Eiffel

    +

    Eiffel tower construction

    +

    Eiffel est un très beau langage objet. Bien plus propre que C++. Mais, à moins que les choses aient changées, il n’est pas très populaire. Derrière lui il n’a pas la communauté de C++. Pour être franc, j’ai préféré travailler en C++. J’ai menti à mes profs de l’époque pour leur faire plaisir. Lorsqu’on viens du C, il est désagréable de changer ses habitudes.

    +

    Java

    +

    Holy Grail from the Monty Python

    +

    On continue vers les langages objets. Alors, à une époque où j’en ai entendu parler, c’était le Graal !

    +

    La portabilité, votre programme marchera partout. Il était orienté objet. Incrusté à l’intérieur il y avait des concepts d’architecture qui empêchent de faire n’importe quoi… Sauf que.

    +

    Sauf qu’il est incroyablement verbeux. Et que les limitations sont très désagréables si on sait ce que l’on fait.

    +

    Par exemple, il n’y a pas d’héritage multiple en Java. Ce qui est en général un choix que je trouve cohérent s’il est bien appuyé par des systèmes qui compensent ce manque. En java, il existe les interfaces. Les interfaces permettent d’ajouter des méthodes à une classe. En aucun cas on ne peut rajouter un attribut autrement qu’en héritant. Cet état de fait m’a vraiment géné.

    +

    Typiquement je faisais une GUI en Java Swing. J’avais créé mon propre système de notification entre objets. Au début je considérais qu’un objet ne devait envoyer des notifications qu’à un seul objet. Ô quelle erreur lorsque je réalisais qu’il fallait non plus gérer un seul objet mais parfois plusieurs. Je changeais mon implémentation d’interface partout, conséquence, des copier/coller dans tous les sens pour mes classes. Les copier/coller qui sont justement un problème censé être évité par les langages orientés objets.

    +

    De plus toujours pour ma GUI, je devais évidemment gérer des threads. Hors, il m’a fallu faire mon propre système de gestion de threads pour éviter les locks, pour les notifications (ce thread à fini, etc…). À l’époque j’utilisais Java 1.5. Normallement ce problème devait être réglé sur Java 1.6. J’espère que c’est le cas, mais avoir ce type de “feature” essentielle oubliée par le langage était assez grave.

    +

    De même, il a fallu attendre très longtemps avant d’avoir des boucles foreach qui rendent le code bien plus lisible.

    +

    Bon, après cette expérience je déconseillerai Java. La portabilité, n’est pas si intéressante que ce qu’on pourrait croire.

    +

    En ce qui concerne les GUI, portable signifie interface fonctionnelle mais médiocre sur toutes les plateformes. Quelque soit le système d’ailleurs (wxWidget, QT, etc…). Donc, pour des applications à distribuer à des tiers, c’est à éviter.

    +

    Le système de Java est très clos. Par contre il résout un très bon problème. Il permet à des développeurs médiocres de travailler en groupe sans faire trop de mal. Et un bon programmeur sera tout de même capable d’y faire des choses très intéressantes. Veuillez noter que je n’ai pas dit que les programmeurs Java sont de mauvais programmeurs, ce n’est pas ce que je pense.

    +

    Objective-C

    +

    Xcode Logo

    +

    Le langage que je n’ai appris et utilisé que pour faire des applications sur les plateformes d’Apple(c). J’ai appris Objective-C après Python. Et je dois avouer que j’ai eu du mal à m’y mettre. Je n’ai pas du tout aimé la syntaxe et pas mal d’autres détails. Mais ça fait parti de ces langages que plus on utilise, plus on aime. En réalité, il y a quelque chose dans ce langage qui fait que tout est bien pensé. Mais surtout, ici, ce n’est pas le langage qui est la meilleure partie, c’est plutôt le framework Cocoa qui lui est le plus souvent associé qui est une merveille. Par rapport à tous les autres framework permettant de fabriquer des GUI, Cocoa est de très loin supérieur. Même si ça semble être des détails sur le papier, en pratique cela fait une grande différence.

    +

    Vraiment jusqu’ici, même si Objective-C reste assez bas niveau, le fait que le typage de ce langage soit dynamique est un vrai plus pour l’interface graphique. Je ne peux que vous encourager à vous accrocher à ce langage et de faire un vrai programme avec. Vous en serez certainement plus ravi qu’il n’y parrait eu début.

    +

    Les langages interprétés modernes

    +

    PHP

    +

    A Jacky Touch Car

    +

    Le petit langage de script que nous utilisions tous pour faire des sites web à l’époque des gifs animées !

    +

    Sympatique, mais sans plus. Apparemment il y a eu pas mal de progrès depuis PHP5, un jour peut-être que j’y reviendrai. Mais, il a derrière lui une réputation de langage pour les “scripts kiddies”. En gros ceux qui ne savent pas coder. Des trous de sécurité de tous les cotés, etc…

    +

    En réalité, PHP est au niveau d’abstration à peine supérieur au C. Et donc, il est beaucoup moins bien organisé que des langages objets, favorisant ainsi la création de bug. Pour les applications web, c’est un vrai problème.

    +

    PHP, reste pour moi le langage de l’injection SQL. J’en fait encore un peu de temps en temps. Et j’ai moi-même dû protéger les accès au SQL pour éviter les injections. Oui, je n’ai pas trouvé de librairie toute prête pour protéger les entrées SQL. Je n’ai pas beaucoup cherché non plus.

    +

    Python

    +

    Python. Do you speak it?

    +

    Alors là, attention ! Révélation !

    +

    Lorsqu’on avait l’habitude de travailler avec des langages compilé, type C++, Java et qu’on passe à Python, on se prend une claque magistrale. La programmation comme elle doit être faite. Tout est si naturel, c’est magique. Oui, c’est si bien que ça. Mais quelque chose d’aussi incroyablement bien doit avoir des inconvénients me dirais-vous.

    +

    Et bien, oui, comme tous les langages de scripts de haut niveau, Python est lent. Attention pas juste un peu lent, comme 2 fois plus lent que du C. Non, de l’ordre de 10 à 20 fois plus lent que le C. Argh… Bon ça reste utilisable pour beaucoup de choses. Mais certaines application lui sont donc interdites.

    +

    Awk

    +

    Des filtres de fichiers à faire. Si ce n’est pas trop compliqué, c’est le langage idéal. Vous avez un fichier et vous voulez savoir quels sont les mots les plus utilisés. Savoir combien de fois un mot est utilisé. Filtrer sous des condition un peu plus compliquées qu’un grep. Super outils. Je l’ai utilisé pour modifier en masse des centaines de fichier XML plus facilement qu’avec du XSLT.

    +

    Perl

    +

    Perl c’est assez magique, mais la syntaxe est tellement désagréable à lire que personne ne peut vraiment aimer programmer dans un environnement de plusieurs personnes en Perl. A moins que tous les autres soient des cadors du Perl. Mais la feature qui tue, les expressions régulières :

    +
    $var =~ s/toto/titi/g
    +

    Va remplacer toto par titi dans la valeur de la variable $var. Et oui, les expressions régulière y sont intégrées directement comme avec sed et awk. Et ça rend le code beacoup plus compact (et parfois illisible). Mais c’est vraiment pas mal. C’est une sorte de awk sous stéroides.

    +

    Ruby

    +

    C’est une sorte de Perl en plus propre. Un mélange de Perl et de Python. Les notion objets y sont plus fortes qu’en Python. Je l’ai beaucoup utilisé, je reste quand même un Pythoniste de préférence. Mais Ruby est vraiment très bien. Par contre en terme d’efficacité, c’est le pire langage utilisé par beaucoup de monde de ce point de vue. C’est le langage qui perd quasiment tous les benchmarks. Par contre c’est un outil parfait pour faire des prototypes. Et si vous voulez faire un prototype de site web, RoR est ce qui se fait de mieux. De l’idée au site, il ne se passera que peu de temps.

    +

    Javascript

    +

    C’est la bonne surprise. Pendant des années, javascript était considéré comme un langage tout bon à vous embéter dans votre navigation web. En réalité, javascript possède beaucoup de qualité des langages de haut niveau. En particulier, il est facille de passer une fonction en paramèter ou de créer des fonctions anonymes (closures). Récemment, il est devenu très rapide et beaucoup de frameworks et de librairies naissent un peu partout.

    +
      +
    • Il y a Cappuccino, Objective-J (comme de l’objective-C mais avec du javascript)
    • +
    • Sproutcore
    • +
    • Spine.js
    • +
    • Backbone.js
    • +
    • jQuery
    • +
    • prototype.js
    • +
    +

    En particulier avec jQuery, on peut faire des appels chainés, très agréables à utiliser. Comme je le disais, c’est une bonne surprise, javascript a été choisi un peu au hasard lors de la création des navigateurs web comme langage de script. Et il s’avère qu’à part sa syntaxe, tout le reste est bien. Heureusement, en ce qui concerne la syntaxe, on peu pallier à ce problème en utilisant CoffeeScript.

    +

    Les langages fonctionnels

    +

    CamL

    +

    J’ai appris CamL à la fac, j’avais trouvé cette expérience très interressante. J’étais plutôt bon, et j’avais les bonnes intuitions mathématiques qui vont avec la programmation fonctionnelle. Mais je dois avouer que je ne l’ai plus jamais utilisé. Simplement, ce type de langage semble si loin de ce qui se fait pour fabriquer des produits que ça me donnais vraiment l’impression d’être un langage pour chercheurs.

    +

    Haskell

    +

    Je suis en train d’apprendre ce langage. Et je dois dire que c’est un vrai plaisir. En général les concepts derrière tous les langages de programmation sont assez limités. Chaque langage y va de son petit lot de nouveau concepts, et en général en une après-midi, c’est appris. Pour haskell, c’est très différent. Je sens bien qu’il va me falloir plusieurs semaines pour maîtriser la bête. Ça doit faire quatre semaines que j’apprend haskell un peut tous les jours et je sais qu’il y a des notions que j’ai juste survollées et qui sont assez incroyables. Les Monades par exemple, est un concept que je n’avais jamais rencontré ailleurs. C’est un super concept. De plus le design du langage en fait un parfait système pour paralléliser les calculs naturellement. haskell sépare la partie “pure” de la partie “impure” de la programmation. À ma connaissance, c’est le seul langage de programmation qui fait ça. Enfin, je prend beaucoup de plaisir à apprendre ce langage. La communauté est aussi très acceuillante. Pas de “L0L! URAN00B!”. Et aussi pas de concession du langage pour devenir populaire. Le langage est bon, voilà tout. Alors qu’en Java et C++, typiquement certain choix ont été fait en dépis du bon sens pour “faire plaisir”.

    +

    Langages originaux

    +

    Metapost

    +

    Metapost est un langage qui permet de programmer des dessins. Le gros plus de metapost, c’est sa capacité de résoudre automatiquement les systèmes d’équations linéaires. Par exemple, si vous écrivez :

    +
    AA=1/3[A,B]
    +

    Il va position le point AA entre A et B. Plus précisément, au barycentre (2A + B)/3.

    +
    X=whatever[A,B]
    +X=whatever[C,D]
    +

    Ce deuxième exemple positionne X à l’intersection des deux segments AB et CD. Vous pouvez aussi voir pas mal d’exemples ici. You could see more example there.

    +

    Cette fonction est très utile. Et à mon avis pas seulement pour afficher des choses. De mon point de vue, les autres langages de programmation devraient penser à rajouter les résolutions automatiques simples.

    +

    zsh

    +

    Oui, zsh est un shell. Mais c’est aussi un langage de script très bien adapté aux traitement de fichiers. Je le recommande chaudement. C’est pour l’instant le meilleur shell que j’ai utilisé. Je le préfère au bash.

    +

    Prolog

    +

    Je n’ai jamais rien fait de conséquent avec Prolog, mais j’ai adoré l’apprendre et l’utiliser. J’ai eu la chance d’apprendre Prolog par Alain Colmerauer lui-même. C’est un langage qui essaye de résoudre les contraintes autant qu’il le peut pour vous. Il en ressort un impression de magie. On ne fait que décrire ce qu’il faut et on ne donne pas d’ordre. Un peu comme la programmation fonctionnelle mais en beaucoup plus puissant.

    +

    Les langages à découvrir

    +

    Il reste encore pas mal de langages et de framework à essayer. Actuellement je pense que je vais passer un moment avec Haskell. Peut-être demain que j’irai apprendre LISP, Scala ou Erlang. Comme je suis plus dans la création de site web, j’irai certainement jeter un coup d’œil à clojure aussi. Et certainement beaucoup d’autres choses.

    +

    Dites moi si vous avez une autre expérience avec ces langages de programmation. Évidement mes impression sont hautement subjectives. Cependant, j’ai utilisé tous les langages dont j’ai parlé.

    +

    [STL]: Standard Tempate Library[GUI]: Graphic User Interface

    +
    + +
    + + RSS + + + + + + +
    + +
    +
    +
    +
    +

    Comments

    +
    + + + comments powered by Disqus +
    +
    +
    + Published on 2011-09-28 +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/latest/index.html b/Scratch/fr/latest/index.html new file mode 100644 index 0000000..49453e0 --- /dev/null +++ b/Scratch/fr/latest/index.html @@ -0,0 +1,83 @@ + + + + + + YBlog - latest + + + + + + + + + + + + +
    + + +
    +

    latest

    +
    +
    +
    +
    + +
    + +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/rss/index.html b/Scratch/fr/rss/index.html new file mode 100644 index 0000000..cefbda9 --- /dev/null +++ b/Scratch/fr/rss/index.html @@ -0,0 +1,97 @@ + + + + + + YBlog - Que sont les flux RSS ? + + + + + + + + + + + + +
    + + +
    +

    Que sont les flux RSS ?

    +
    +
    +
    +
    +

    Lorsque vous cliquez sur ce logo :

    +

    rss

    +

    On vous propose de vous abonner au flux RSS. Mais de quoi s’agit il ?

    +

    Si vous n’êtes pas anglophobe je vous recommande la lecture de what is rss ou encore mieux, de regarder cette vidéo RSS explained.

    +
    +

    Mon explication

    +

    Il s’agit d’un moyen facile d’agréger dans un seul endroit toutes les mises à jours de tous les sites qui vous intéressent.

    +

    choisir un client

    +

    Tout d’abord, il faut choisir un client de flux RSS. Aujourd’hui il existe de nombreux client en ligne. C’est-à-dire des sites web qui vont s’occuper du regroupement. Ces client s’appellent des “aggregator”.

    +

    Personnellement j’utilise Netvibes. J’en ai essayé vraiment beaucoup, et il reste de loin mon préféré.

    +

    Évidemment Google propose son client aussi : Google Reader. S’il reste adapté pour les contenus pour lesquels on ne veut rien perdre. Il est moins agréable d’utilisation lorsque l’on s’abonne à des flux qui proposent une vingtaine de nouveaux liens par jour.

    +

    S’abonner aux flux d’un site

    +

    Donc une fois que l’on a choisi son client, il suffit pour s’abonner de cliquer sur l’icône d’abonnement. Soit il est bien visible sur la page, soit tout en haut dans la barre des tâches.

    +

    Récupérer les “news”

    +

    Ensuite lorsque vous utilisez votre client RSS les nouvelles provenant du blog se mettront à jour. Ainsi, il n’y a plus besoin d’aller sur les sites intéressants pour voir s’il n’y a rien de neuf. Ce sont eux qui vous donne leur dernières nouvelles.

    +
    + +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/softwares/index.html b/Scratch/fr/softwares/index.html new file mode 100644 index 0000000..0c82e1b --- /dev/null +++ b/Scratch/fr/softwares/index.html @@ -0,0 +1,87 @@ + + + + + + YBlog - Softwares + + + + + + + + + + + + +
    + + +
    +

    Softwares

    +
    +
    + + +
    + + + + + + + + diff --git a/Scratch/fr/softwares/yaquabubbles/index.html b/Scratch/fr/softwares/yaquabubbles/index.html new file mode 100644 index 0000000..929df25 --- /dev/null +++ b/Scratch/fr/softwares/yaquabubbles/index.html @@ -0,0 +1,85 @@ + + + + + + YBlog - YAquaBubbles + + + + + + + + + + + + +
    + + +
    +

    YAquaBubbles

    +
    +
    +
    +
    +

    Screenshot

    +

    YAquaBubbles est un économiseur d’écran réalisé avec QuartzComposer. Il s’agissait d’un simple essai mais le résultat était plaisant.

    +

    YAquaBubbles.dmg

    +
    + +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/softwares/yclock/index.html b/Scratch/fr/softwares/yclock/index.html new file mode 100644 index 0000000..3a67fcc --- /dev/null +++ b/Scratch/fr/softwares/yclock/index.html @@ -0,0 +1,85 @@ + + + + + + YBlog - YClock + + + + + + + + + + + + +
    + + +
    +

    YClock

    +
    +
    +
    +
    +

    Screenshot

    +

    YClock est un économiseur d’écran qui vous donne l’heure.i Il a trois thèmes clair, rouge et noir. Il utilise une base de QuartzComposition + du code objective-C pour la gestion du nombre d’images par seconde.

    +

    YClock.dmg

    +
    + +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/softwares/ypassword/index.html b/Scratch/fr/softwares/ypassword/index.html new file mode 100644 index 0000000..3ca0493 --- /dev/null +++ b/Scratch/fr/softwares/ypassword/index.html @@ -0,0 +1,93 @@ + + + + + + YBlog - YPassword + + + + + + + + + + + + +
    + + +
    +

    YPassword

    +
    +
    +
    +
    +

    Une gestion simple, sécurisée et portable de ses mots de passes web.

    +

    Souvenez vous d’un seul mot de passe de bonne qualité, le reste suis.

    +

    Ici vous trouverez :

    + +

    Dans peu de temps je créerai une application iPhone pour YPassword.

    +
    + +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/softwares/ypassword/iphoneweb/index.html b/Scratch/fr/softwares/ypassword/iphoneweb/index.html new file mode 100644 index 0000000..37e253a --- /dev/null +++ b/Scratch/fr/softwares/ypassword/iphoneweb/index.html @@ -0,0 +1,91 @@ + + + + + + YBlog - YPassword + + + + + + + + + + + + +
    + + +
    +

    YPassword

    +
    +
    +
    +
    +
    + +
    + + +
    + +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/softwares/ypassword/web/index.html b/Scratch/fr/softwares/ypassword/web/index.html new file mode 100644 index 0000000..c22dc55 --- /dev/null +++ b/Scratch/fr/softwares/ypassword/web/index.html @@ -0,0 +1,91 @@ + + + + + + YBlog - YPassword + + + + + + + + + + + + +
    + + +
    +

    YPassword

    +
    +
    +
    +
    +
    + +
    + + +
    + +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/fr/validation/index.html b/Scratch/fr/validation/index.html new file mode 100644 index 0000000..98e0cda --- /dev/null +++ b/Scratch/fr/validation/index.html @@ -0,0 +1,88 @@ + + + + + + YBlog - Validation + + + + + + + + + + + + +
    + + +
    +

    Validation

    +
    +
    +
    +
    +

    Une explication rapide du pourquoi il y a des erreurs de validation de mes pages.

    +

    Je voulais utiliser box-shadows et border-radius

    +

    J’ai donc préféré avoir un approche pragamatique que dogmatique.

    +

    Utiliser ces propriétés me fait perdre la validation CSS mais fonctionne très bien avec les navigateurs récents (Safari 4 et Firefox 3.5 au moment de l’écriture de ces lignes)

    +

    Si vous n’utilisez pas ces navigateur les pages s’affichent correctement mais sans ces effets qui n’ont pour but que d’améliorer l’aspect général de la page.

    +

    Par contre je suis plutôt un partisant de la validation et c’est pourquoi il y a toujours les liens. Tout valide à l’exception des propriétés commençant par -moz et -webkit.

    +
    + +
    + +
    + Yann Esposito© +
    +
    + Done with + Vim + & + Hakyll +
    +
    +
    + +
    + + + + + + + + diff --git a/Scratch/img/about/avatar.png b/Scratch/img/about/avatar.png new file mode 100644 index 0000000..3ce7c4e Binary files /dev/null and b/Scratch/img/about/avatar.png differ diff --git a/Scratch/img/about/cv/cv.png b/Scratch/img/about/cv/cv.png new file mode 100644 index 0000000..e202513 Binary files /dev/null and b/Scratch/img/about/cv/cv.png differ diff --git a/Scratch/img/about/yann1.jpg b/Scratch/img/about/yann1.jpg new file mode 100644 index 0000000..62232c4 Binary files /dev/null and b/Scratch/img/about/yann1.jpg differ diff --git a/Scratch/img/blog/06_How_I_use_git/central_architecture.png b/Scratch/img/blog/06_How_I_use_git/central_architecture.png new file mode 100644 index 0000000..3def16e Binary files /dev/null and b/Scratch/img/blog/06_How_I_use_git/central_architecture.png differ diff --git a/Scratch/img/blog/07_Screensaver_compilation_option_for_Snow_Leopard/xcodeConfig.png b/Scratch/img/blog/07_Screensaver_compilation_option_for_Snow_Leopard/xcodeConfig.png new file mode 100644 index 0000000..12a1aab Binary files /dev/null and b/Scratch/img/blog/07_Screensaver_compilation_option_for_Snow_Leopard/xcodeConfig.png differ diff --git a/Scratch/img/blog/2010-03-23-Encapsulate-git/branch_hierarchy.png b/Scratch/img/blog/2010-03-23-Encapsulate-git/branch_hierarchy.png new file mode 100644 index 0000000..f018ae9 Binary files /dev/null and b/Scratch/img/blog/2010-03-23-Encapsulate-git/branch_hierarchy.png differ diff --git a/Scratch/img/blog/2010-03-23-Encapsulate-git/dynamic_branching.png b/Scratch/img/blog/2010-03-23-Encapsulate-git/dynamic_branching.png new file mode 100644 index 0000000..2ab981a Binary files /dev/null and b/Scratch/img/blog/2010-03-23-Encapsulate-git/dynamic_branching.png differ diff --git a/Scratch/img/blog/2010-06-17-track-events-with-google-analytics/GA_events.png b/Scratch/img/blog/2010-06-17-track-events-with-google-analytics/GA_events.png new file mode 100644 index 0000000..5525103 Binary files /dev/null and b/Scratch/img/blog/2010-06-17-track-events-with-google-analytics/GA_events.png differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/Data b/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/Data new file mode 100644 index 0000000..bfe3165 Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/Data differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/QuickLook/Preview.jpg b/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/QuickLook/Preview.jpg new file mode 100644 index 0000000..f30510f Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/QuickLook/Preview.jpg differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/QuickLook/Thumbnail.jpg b/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/QuickLook/Thumbnail.jpg new file mode 100644 index 0000000..f30510f Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/QuickLook/Thumbnail.jpg differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.png b/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.png new file mode 100644 index 0000000..423067f Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.png differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/Data b/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/Data new file mode 100644 index 0000000..6f86add Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/Data differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/QuickLook/Preview.jpg b/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/QuickLook/Preview.jpg new file mode 100644 index 0000000..355c531 Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/QuickLook/Preview.jpg differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/QuickLook/Thumbnail.jpg b/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/QuickLook/Thumbnail.jpg new file mode 100644 index 0000000..355c531 Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/QuickLook/Thumbnail.jpg differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.png b/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.png new file mode 100644 index 0000000..e95f161 Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.png differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/Data b/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/Data new file mode 100644 index 0000000..ce6cd9e Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/Data differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/QuickLook/Preview.jpg b/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/QuickLook/Preview.jpg new file mode 100644 index 0000000..3d33553 Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/QuickLook/Preview.jpg differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/QuickLook/Thumbnail.jpg b/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/QuickLook/Thumbnail.jpg new file mode 100644 index 0000000..36ccbf3 Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/QuickLook/Thumbnail.jpg differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.png b/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.png new file mode 100644 index 0000000..8c76b5c Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.png differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/Data b/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/Data new file mode 100644 index 0000000..ef5765d Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/Data differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/QuickLook/Preview.jpg b/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/QuickLook/Preview.jpg new file mode 100644 index 0000000..2e63929 Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/QuickLook/Preview.jpg differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/QuickLook/Thumbnail.jpg b/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/QuickLook/Thumbnail.jpg new file mode 100644 index 0000000..0c295e0 Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/QuickLook/Thumbnail.jpg differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.png b/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.png new file mode 100644 index 0000000..a0f21c8 Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.png differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/Data b/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/Data new file mode 100644 index 0000000..1acf118 Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/Data differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/QuickLook/Preview.jpg b/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/QuickLook/Preview.jpg new file mode 100644 index 0000000..31bd451 Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/QuickLook/Preview.jpg differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/QuickLook/Thumbnail.jpg b/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/QuickLook/Thumbnail.jpg new file mode 100644 index 0000000..2458578 Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/QuickLook/Thumbnail.jpg differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.png b/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.png new file mode 100644 index 0000000..82e3180 Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.png differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_3_angles.png b/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_3_angles.png new file mode 100644 index 0000000..043c0eb Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_3_angles.png differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/Data b/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/Data new file mode 100644 index 0000000..660193d Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/Data differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/QuickLook/Preview.jpg b/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/QuickLook/Preview.jpg new file mode 100644 index 0000000..07b29cb Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/QuickLook/Preview.jpg differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/QuickLook/Thumbnail.jpg b/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/QuickLook/Thumbnail.jpg new file mode 100644 index 0000000..07b29cb Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/QuickLook/Thumbnail.jpg differ diff --git a/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.png b/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.png new file mode 100644 index 0000000..3abbb7a Binary files /dev/null and b/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.png differ diff --git a/Scratch/img/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/main.png b/Scratch/img/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/main.png new file mode 100644 index 0000000..d083349 Binary files /dev/null and b/Scratch/img/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/main.png differ diff --git a/Scratch/img/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/sign_icon.png b/Scratch/img/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/sign_icon.png new file mode 100644 index 0000000..ac636c5 Binary files /dev/null and b/Scratch/img/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/sign_icon.png differ diff --git a/Scratch/img/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/main.png b/Scratch/img/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/main.png new file mode 100644 index 0000000..4ea7161 Binary files /dev/null and b/Scratch/img/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/main.png differ diff --git a/Scratch/img/blog/2011-04-20-Now-hosted-on-github/main.png b/Scratch/img/blog/2011-04-20-Now-hosted-on-github/main.png new file mode 100644 index 0000000..8075d5d Binary files /dev/null and b/Scratch/img/blog/2011-04-20-Now-hosted-on-github/main.png differ diff --git a/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3DMandelbulbDetail.png b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3DMandelbulbDetail.png new file mode 100644 index 0000000..cf7fc33 Binary files /dev/null and b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3DMandelbulbDetail.png differ diff --git a/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3DMandelbulbDetail2.png b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3DMandelbulbDetail2.png new file mode 100644 index 0000000..2ee21ad Binary files /dev/null and b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3DMandelbulbDetail2.png differ diff --git a/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3D_mandel.png b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3D_mandel.png new file mode 100644 index 0000000..59b4cb0 Binary files /dev/null and b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3D_mandel.png differ diff --git a/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/BenoitBMandelbrot.jpg b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/BenoitBMandelbrot.jpg new file mode 100644 index 0000000..25e3837 Binary files /dev/null and b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/BenoitBMandelbrot.jpg differ diff --git a/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/GoldenMandelbulb.png b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/GoldenMandelbulb.png new file mode 100644 index 0000000..e45df7c Binary files /dev/null and b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/GoldenMandelbulb.png differ diff --git a/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGLMandelEdge.png b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGLMandelEdge.png new file mode 100644 index 0000000..bbf26eb Binary files /dev/null and b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGLMandelEdge.png differ diff --git a/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGLMandelEdges.png b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGLMandelEdges.png new file mode 100644 index 0000000..2298a6f Binary files /dev/null and b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGLMandelEdges.png differ diff --git a/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGL_Plan.png b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGL_Plan.png new file mode 100644 index 0000000..7755f55 Binary files /dev/null and b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGL_Plan.png differ diff --git a/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/hglmandel_v01.png b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/hglmandel_v01.png new file mode 100644 index 0000000..bacd408 Binary files /dev/null and b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/hglmandel_v01.png differ diff --git a/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/hglmandel_v01_too_wide.png b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/hglmandel_v01_too_wide.png new file mode 100644 index 0000000..db6fb86 Binary files /dev/null and b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/hglmandel_v01_too_wide.png differ diff --git a/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/main.png b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/main.png new file mode 100644 index 0000000..0cb82c7 Binary files /dev/null and b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/main.png differ diff --git a/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/mandelbrot_3D.png b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/mandelbrot_3D.png new file mode 100644 index 0000000..3a07dcc Binary files /dev/null and b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/mandelbrot_3D.png differ diff --git a/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/triangles.png b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/triangles.png new file mode 100644 index 0000000..9f9fe29 Binary files /dev/null and b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/triangles.png differ diff --git a/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/triangles.svg b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/triangles.svg new file mode 100644 index 0000000..a82d5c3 --- /dev/null +++ b/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/triangles.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + p1 + p2 + p3 + p4 + + diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/Haskell-logo.png b/Scratch/img/blog/Haskell-the-Hard-Way/Haskell-logo.png new file mode 100644 index 0000000..1a5cb55 Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/Haskell-logo.png differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/dali_reve.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/dali_reve.jpg new file mode 100644 index 0000000..274379d Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/dali_reve.jpg differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/dangerous_book.png b/Scratch/img/blog/Haskell-the-Hard-Way/dangerous_book.png new file mode 100644 index 0000000..b7b28cf Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/dangerous_book.png differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/escher_infinite_lizards.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/escher_infinite_lizards.jpg new file mode 100644 index 0000000..58bbfbd Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/escher_infinite_lizards.jpg differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/escher_polygon.png b/Scratch/img/blog/Haskell-the-Hard-Way/escher_polygon.png new file mode 100644 index 0000000..9022eec Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/escher_polygon.png differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/golconde.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/golconde.jpg new file mode 100644 index 0000000..95d440c Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/golconde.jpg differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/hr_giger_biomechanicallandscape_500.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/hr_giger_biomechanicallandscape_500.jpg new file mode 100644 index 0000000..2dd24f8 Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/hr_giger_biomechanicallandscape_500.jpg differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/jocker_pencil_trick.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/jocker_pencil_trick.jpg new file mode 100644 index 0000000..18de553 Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/jocker_pencil_trick.jpg differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/kandinsky_gugg.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/kandinsky_gugg.jpg new file mode 100644 index 0000000..cc5c049 Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/kandinsky_gugg.jpg differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/learn_haskell_mordor.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/learn_haskell_mordor.jpg new file mode 100644 index 0000000..9a0d9a0 Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/learn_haskell_mordor.jpg differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/magritte-l-arbre.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/magritte-l-arbre.jpg new file mode 100644 index 0000000..9168c2a Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/magritte-l-arbre.jpg differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/magritte_carte_blanche.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/magritte_carte_blanche.jpg new file mode 100644 index 0000000..6004363 Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/magritte_carte_blanche.jpg differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/magritte_pipe.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/magritte_pipe.jpg new file mode 100644 index 0000000..9cecdc0 Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/magritte_pipe.jpg differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/magritte_pleasure_principle.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/magritte_pleasure_principle.jpg new file mode 100644 index 0000000..e817424 Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/magritte_pleasure_principle.jpg differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/munch_TheScream.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/munch_TheScream.jpg new file mode 100644 index 0000000..432ddd1 Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/munch_TheScream.jpg differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/picasso_owl.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/picasso_owl.jpg new file mode 100644 index 0000000..5732d0c Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/picasso_owl.jpg differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/salvador-dali-the-madonna-of-port-lligat.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/salvador-dali-the-madonna-of-port-lligat.jpg new file mode 100644 index 0000000..4f858c6 Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/salvador-dali-the-madonna-of-port-lligat.jpg differ diff --git a/Scratch/img/blog/Haskell-the-Hard-Way/yo_dawg_tree.jpg b/Scratch/img/blog/Haskell-the-Hard-Way/yo_dawg_tree.jpg new file mode 100644 index 0000000..b83be19 Binary files /dev/null and b/Scratch/img/blog/Haskell-the-Hard-Way/yo_dawg_tree.jpg differ diff --git a/Scratch/img/blog/Higher-order-function-in-zsh/main.jpg b/Scratch/img/blog/Higher-order-function-in-zsh/main.jpg new file mode 100644 index 0000000..6432838 Binary files /dev/null and b/Scratch/img/blog/Higher-order-function-in-zsh/main.jpg differ diff --git a/Scratch/img/blog/Higher-order-function-in-zsh/src/main.jpg b/Scratch/img/blog/Higher-order-function-in-zsh/src/main.jpg new file mode 100644 index 0000000..aec86bb Binary files /dev/null and b/Scratch/img/blog/Higher-order-function-in-zsh/src/main.jpg differ diff --git a/Scratch/img/blog/Learn-Vim-Progressively/append-to-many-lines.gif b/Scratch/img/blog/Learn-Vim-Progressively/append-to-many-lines.gif new file mode 100644 index 0000000..a1f5c31 Binary files /dev/null and b/Scratch/img/blog/Learn-Vim-Progressively/append-to-many-lines.gif differ diff --git a/Scratch/img/blog/Learn-Vim-Progressively/autoindent.gif b/Scratch/img/blog/Learn-Vim-Progressively/autoindent.gif new file mode 100644 index 0000000..a8f9661 Binary files /dev/null and b/Scratch/img/blog/Learn-Vim-Progressively/autoindent.gif differ diff --git a/Scratch/img/blog/Learn-Vim-Progressively/completion.gif b/Scratch/img/blog/Learn-Vim-Progressively/completion.gif new file mode 100644 index 0000000..f5c5463 Binary files /dev/null and b/Scratch/img/blog/Learn-Vim-Progressively/completion.gif differ diff --git a/Scratch/img/blog/Learn-Vim-Progressively/line_moves.jpg b/Scratch/img/blog/Learn-Vim-Progressively/line_moves.jpg new file mode 100644 index 0000000..9fef408 Binary files /dev/null and b/Scratch/img/blog/Learn-Vim-Progressively/line_moves.jpg differ diff --git a/Scratch/img/blog/Learn-Vim-Progressively/line_moves.txt b/Scratch/img/blog/Learn-Vim-Progressively/line_moves.txt new file mode 100644 index 0000000..c598330 --- /dev/null +++ b/Scratch/img/blog/Learn-Vim-Progressively/line_moves.txt @@ -0,0 +1,6 @@ + + + +0 ^ fi t) 4fi g_ $ +│ │ │ │ │ │ │ + x = (name_1,vision_3); #this is a comment. diff --git a/Scratch/img/blog/Learn-Vim-Progressively/macros.gif b/Scratch/img/blog/Learn-Vim-Progressively/macros.gif new file mode 100644 index 0000000..a6aec08 Binary files /dev/null and b/Scratch/img/blog/Learn-Vim-Progressively/macros.gif differ diff --git a/Scratch/img/blog/Learn-Vim-Progressively/rectangular-blocks.gif b/Scratch/img/blog/Learn-Vim-Progressively/rectangular-blocks.gif new file mode 100644 index 0000000..d96224c Binary files /dev/null and b/Scratch/img/blog/Learn-Vim-Progressively/rectangular-blocks.gif differ diff --git a/Scratch/img/blog/Learn-Vim-Progressively/six-million-dollar-man-a-bionic-christmas-carol-17.jpg b/Scratch/img/blog/Learn-Vim-Progressively/six-million-dollar-man-a-bionic-christmas-carol-17.jpg new file mode 100644 index 0000000..c5fd267 Binary files /dev/null and b/Scratch/img/blog/Learn-Vim-Progressively/six-million-dollar-man-a-bionic-christmas-carol-17.jpg differ diff --git a/Scratch/img/blog/Learn-Vim-Progressively/split.gif b/Scratch/img/blog/Learn-Vim-Progressively/split.gif new file mode 100644 index 0000000..9013c11 Binary files /dev/null and b/Scratch/img/blog/Learn-Vim-Progressively/split.gif differ diff --git a/Scratch/img/blog/Learn-Vim-Progressively/textobjects.png b/Scratch/img/blog/Learn-Vim-Progressively/textobjects.png new file mode 100644 index 0000000..a514f07 Binary files /dev/null and b/Scratch/img/blog/Learn-Vim-Progressively/textobjects.png differ diff --git a/Scratch/img/blog/Learn-Vim-Progressively/the_six_million_dollar_man_image_04.jpg b/Scratch/img/blog/Learn-Vim-Progressively/the_six_million_dollar_man_image_04.jpg new file mode 100644 index 0000000..d7dd838 Binary files /dev/null and b/Scratch/img/blog/Learn-Vim-Progressively/the_six_million_dollar_man_image_04.jpg differ diff --git a/Scratch/img/blog/Learn-Vim-Progressively/uber_leet_use_vim.jpg b/Scratch/img/blog/Learn-Vim-Progressively/uber_leet_use_vim.jpg new file mode 100644 index 0000000..97dfef7 Binary files /dev/null and b/Scratch/img/blog/Learn-Vim-Progressively/uber_leet_use_vim.jpg differ diff --git a/Scratch/img/blog/Learn-Vim-Progressively/word_moves.jpg b/Scratch/img/blog/Learn-Vim-Progressively/word_moves.jpg new file mode 100644 index 0000000..1f69af1 Binary files /dev/null and b/Scratch/img/blog/Learn-Vim-Progressively/word_moves.jpg differ diff --git a/Scratch/img/blog/Lost_Highway_Demistified/intro.jpg b/Scratch/img/blog/Lost_Highway_Demistified/intro.jpg new file mode 100644 index 0000000..1491f20 Binary files /dev/null and b/Scratch/img/blog/Lost_Highway_Demistified/intro.jpg differ diff --git a/Scratch/img/blog/Lost_Highway_Demistified/mysteryman.jpg b/Scratch/img/blog/Lost_Highway_Demistified/mysteryman.jpg new file mode 100644 index 0000000..1de2c35 Binary files /dev/null and b/Scratch/img/blog/Lost_Highway_Demistified/mysteryman.jpg differ diff --git a/Scratch/img/blog/Lost_Highway_Demistified/rorschach.gif b/Scratch/img/blog/Lost_Highway_Demistified/rorschach.gif new file mode 100644 index 0000000..331ad49 Binary files /dev/null and b/Scratch/img/blog/Lost_Highway_Demistified/rorschach.gif differ diff --git a/Scratch/img/blog/Password-Management/main.png b/Scratch/img/blog/Password-Management/main.png new file mode 100644 index 0000000..9c36581 Binary files /dev/null and b/Scratch/img/blog/Password-Management/main.png differ diff --git a/Scratch/img/blog/SVG-and-m4-fractals/compilelogo.sh b/Scratch/img/blog/SVG-and-m4-fractals/compilelogo.sh new file mode 100755 index 0000000..2c57cec --- /dev/null +++ b/Scratch/img/blog/SVG-and-m4-fractals/compilelogo.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env zsh +m4 main.m4 > main.svg && convert main.svg main.png diff --git a/Scratch/img/blog/SVG-and-m4-fractals/main.m4 b/Scratch/img/blog/SVG-and-m4-fractals/main.m4 new file mode 100644 index 0000000..39ab18c --- /dev/null +++ b/Scratch/img/blog/SVG-and-m4-fractals/main.m4 @@ -0,0 +1,43 @@ + + + + + + λ + + + esod + + + + YTRANSCOMPLETE(1,0) + YTRANSCOMPLETE(2,1) + YTRANSCOMPLETE(3,2) + YTRANSCOMPLETE(4,3) + YTRANSCOMPLETE(5,4) + diff --git a/Scratch/img/blog/SVG-and-m4-fractals/main.png b/Scratch/img/blog/SVG-and-m4-fractals/main.png new file mode 100644 index 0000000..799f10a Binary files /dev/null and b/Scratch/img/blog/SVG-and-m4-fractals/main.png differ diff --git a/Scratch/img/blog/SVG-and-m4-fractals/main.svg b/Scratch/img/blog/SVG-and-m4-fractals/main.svg new file mode 100644 index 0000000..1ce6726 --- /dev/null +++ b/Scratch/img/blog/SVG-and-m4-fractals/main.svg @@ -0,0 +1,71 @@ + + + + + + λ + + + esod + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Scratch/img/blog/SVG-and-m4-fractals/warp-benchmark.png b/Scratch/img/blog/SVG-and-m4-fractals/warp-benchmark.png new file mode 100644 index 0000000..b7d1a3f Binary files /dev/null and b/Scratch/img/blog/SVG-and-m4-fractals/warp-benchmark.png differ diff --git a/Scratch/img/blog/Typography-and-the-Web/first_latex_screenshot.png b/Scratch/img/blog/Typography-and-the-Web/first_latex_screenshot.png new file mode 100644 index 0000000..ccb1721 Binary files /dev/null and b/Scratch/img/blog/Typography-and-the-Web/first_latex_screenshot.png differ diff --git a/Scratch/img/blog/Typography-and-the-Web/first_sc_screenshot.png b/Scratch/img/blog/Typography-and-the-Web/first_sc_screenshot.png new file mode 100644 index 0000000..a7430ad Binary files /dev/null and b/Scratch/img/blog/Typography-and-the-Web/first_sc_screenshot.png differ diff --git a/Scratch/img/blog/Typography-and-the-Web/ligatures.png b/Scratch/img/blog/Typography-and-the-Web/ligatures.png new file mode 100644 index 0000000..8f9c28c Binary files /dev/null and b/Scratch/img/blog/Typography-and-the-Web/ligatures.png differ diff --git a/Scratch/img/blog/Typography-and-the-Web/xelatex_ligatures.jpg b/Scratch/img/blog/Typography-and-the-Web/xelatex_ligatures.jpg new file mode 100644 index 0000000..fcb068c Binary files /dev/null and b/Scratch/img/blog/Typography-and-the-Web/xelatex_ligatures.jpg differ diff --git a/Scratch/img/blog/Yesod-excellent-ideas/compilelogo.sh b/Scratch/img/blog/Yesod-excellent-ideas/compilelogo.sh new file mode 100755 index 0000000..3396531 --- /dev/null +++ b/Scratch/img/blog/Yesod-excellent-ideas/compilelogo.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env zsh +m4 dessin.m4 > dessin.svg && convert dessin.svg main.png diff --git a/Scratch/img/blog/Yesod-excellent-ideas/dessin.m4 b/Scratch/img/blog/Yesod-excellent-ideas/dessin.m4 new file mode 100644 index 0000000..ade366c --- /dev/null +++ b/Scratch/img/blog/Yesod-excellent-ideas/dessin.m4 @@ -0,0 +1,48 @@ + + + + + + + λ + + esod + + + + YTRANSCOMPLETE(1,0) + YTRANSCOMPLETE(2,1) + YTRANSCOMPLETE(3,2) + YTRANSCOMPLETE(4,3) + YTRANSCOMPLETE(5,4) + diff --git a/Scratch/img/blog/Yesod-excellent-ideas/dessin.svg b/Scratch/img/blog/Yesod-excellent-ideas/dessin.svg new file mode 100644 index 0000000..a3d5422 --- /dev/null +++ b/Scratch/img/blog/Yesod-excellent-ideas/dessin.svg @@ -0,0 +1,76 @@ + + + + + + + λ + + esod + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Scratch/img/blog/Yesod-excellent-ideas/main.png b/Scratch/img/blog/Yesod-excellent-ideas/main.png new file mode 100644 index 0000000..da21c4a Binary files /dev/null and b/Scratch/img/blog/Yesod-excellent-ideas/main.png differ diff --git a/Scratch/img/blog/Yesod-excellent-ideas/main.svg b/Scratch/img/blog/Yesod-excellent-ideas/main.svg new file mode 100644 index 0000000..d9ea957 --- /dev/null +++ b/Scratch/img/blog/Yesod-excellent-ideas/main.svg @@ -0,0 +1,397 @@ + + + + + + + + + + image/svg+xml + + + + + + + + λ + λ + λ + + λ + λ + λ + λ + λ + λ + λ + λ + λ + λ + λ + λ + λ + λ + esod + λ + + λ + λ + λ + + λ + λ + + diff --git a/Scratch/img/blog/Yesod-excellent-ideas/yesod-logo.svg b/Scratch/img/blog/Yesod-excellent-ideas/yesod-logo.svg new file mode 100644 index 0000000..ff84487 --- /dev/null +++ b/Scratch/img/blog/Yesod-excellent-ideas/yesod-logo.svg @@ -0,0 +1,376 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + λ + + + + + esod + + + + + + esod + + + + + + esod + + + + + + esod + + + + + + esod + + + + + + esod + + + + + + esod + + + diff --git a/Scratch/img/blog/Yesod-tutorial-for-newbies/flying_neo.jpg b/Scratch/img/blog/Yesod-tutorial-for-newbies/flying_neo.jpg new file mode 100644 index 0000000..83f3876 Binary files /dev/null and b/Scratch/img/blog/Yesod-tutorial-for-newbies/flying_neo.jpg differ diff --git a/Scratch/img/blog/Yesod-tutorial-for-newbies/haskell-benchmark.png b/Scratch/img/blog/Yesod-tutorial-for-newbies/haskell-benchmark.png new file mode 100644 index 0000000..338a041 Binary files /dev/null and b/Scratch/img/blog/Yesod-tutorial-for-newbies/haskell-benchmark.png differ diff --git a/Scratch/img/blog/Yesod-tutorial-for-newbies/mirror.jpg b/Scratch/img/blog/Yesod-tutorial-for-newbies/mirror.jpg new file mode 100644 index 0000000..8f78652 Binary files /dev/null and b/Scratch/img/blog/Yesod-tutorial-for-newbies/mirror.jpg differ diff --git a/Scratch/img/blog/Yesod-tutorial-for-newbies/neo_bullet_proof.jpg b/Scratch/img/blog/Yesod-tutorial-for-newbies/neo_bullet_proof.jpg new file mode 100644 index 0000000..3bddd1a Binary files /dev/null and b/Scratch/img/blog/Yesod-tutorial-for-newbies/neo_bullet_proof.jpg differ diff --git a/Scratch/img/blog/Yesod-tutorial-for-newbies/owl_draw.png b/Scratch/img/blog/Yesod-tutorial-for-newbies/owl_draw.png new file mode 100644 index 0000000..2b28cd6 Binary files /dev/null and b/Scratch/img/blog/Yesod-tutorial-for-newbies/owl_draw.png differ diff --git a/Scratch/img/blog/Yesod-tutorial-for-newbies/snap-benchmark.png b/Scratch/img/blog/Yesod-tutorial-for-newbies/snap-benchmark.png new file mode 100644 index 0000000..4ab45a9 Binary files /dev/null and b/Scratch/img/blog/Yesod-tutorial-for-newbies/snap-benchmark.png differ diff --git a/Scratch/img/blog/Yesod-tutorial-for-newbies/thousands_smiths.jpg b/Scratch/img/blog/Yesod-tutorial-for-newbies/thousands_smiths.jpg new file mode 100644 index 0000000..bbee166 Binary files /dev/null and b/Scratch/img/blog/Yesod-tutorial-for-newbies/thousands_smiths.jpg differ diff --git a/Scratch/img/blog/Yesod-tutorial-for-newbies/warp-benchmark.png b/Scratch/img/blog/Yesod-tutorial-for-newbies/warp-benchmark.png new file mode 100644 index 0000000..bbee2c9 Binary files /dev/null and b/Scratch/img/blog/Yesod-tutorial-for-newbies/warp-benchmark.png differ diff --git a/Scratch/img/blog/mvc/Screenshot_v0.png b/Scratch/img/blog/mvc/Screenshot_v0.png new file mode 100644 index 0000000..c0fbab6 Binary files /dev/null and b/Scratch/img/blog/mvc/Screenshot_v0.png differ diff --git a/Scratch/img/blog/programming-language-experience/C.jpg b/Scratch/img/blog/programming-language-experience/C.jpg new file mode 100644 index 0000000..5f67515 Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/C.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/basic.gif b/Scratch/img/blog/programming-language-experience/basic.gif new file mode 100644 index 0000000..63388d8 Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/basic.gif differ diff --git a/Scratch/img/blog/programming-language-experience/basic.jpg b/Scratch/img/blog/programming-language-experience/basic.jpg new file mode 100644 index 0000000..a2c49d0 Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/basic.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/cplusplus.jpg b/Scratch/img/blog/programming-language-experience/cplusplus.jpg new file mode 100644 index 0000000..48e8dfa Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/cplusplus.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/dragon.jpg b/Scratch/img/blog/programming-language-experience/dragon.jpg new file mode 100644 index 0000000..3f9a26f Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/dragon.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/eiffel.jpg b/Scratch/img/blog/programming-language-experience/eiffel.jpg new file mode 100644 index 0000000..95795a7 Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/eiffel.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/grail.jpg b/Scratch/img/blog/programming-language-experience/grail.jpg new file mode 100644 index 0000000..c7d139a Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/grail.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/php.jpg b/Scratch/img/blog/programming-language-experience/php.jpg new file mode 100644 index 0000000..400789e Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/php.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/python.jpg b/Scratch/img/blog/programming-language-experience/python.jpg new file mode 100644 index 0000000..a07242e Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/python.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/src/C.jpg b/Scratch/img/blog/programming-language-experience/src/C.jpg new file mode 100644 index 0000000..aea3901 Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/src/C.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/src/basic.gif b/Scratch/img/blog/programming-language-experience/src/basic.gif new file mode 100644 index 0000000..21a3385 Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/src/basic.gif differ diff --git a/Scratch/img/blog/programming-language-experience/src/basic.jpg b/Scratch/img/blog/programming-language-experience/src/basic.jpg new file mode 100644 index 0000000..887d7c0 Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/src/basic.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/src/cplusplus.jpg b/Scratch/img/blog/programming-language-experience/src/cplusplus.jpg new file mode 100644 index 0000000..209834c Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/src/cplusplus.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/src/dragon.jpg b/Scratch/img/blog/programming-language-experience/src/dragon.jpg new file mode 100644 index 0000000..ec79f87 Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/src/dragon.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/src/eiffel.jpg b/Scratch/img/blog/programming-language-experience/src/eiffel.jpg new file mode 100644 index 0000000..2a8af62 Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/src/eiffel.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/src/grail.jpg b/Scratch/img/blog/programming-language-experience/src/grail.jpg new file mode 100644 index 0000000..af67497 Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/src/grail.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/src/php.jpg b/Scratch/img/blog/programming-language-experience/src/php.jpg new file mode 100644 index 0000000..09533f7 Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/src/php.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/src/python.jpg b/Scratch/img/blog/programming-language-experience/src/python.jpg new file mode 100644 index 0000000..ac9e097 Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/src/python.jpg differ diff --git a/Scratch/img/blog/programming-language-experience/src/xcode_logo.png b/Scratch/img/blog/programming-language-experience/src/xcode_logo.png new file mode 100644 index 0000000..b71c043 Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/src/xcode_logo.png differ diff --git a/Scratch/img/blog/programming-language-experience/xcode_logo.png b/Scratch/img/blog/programming-language-experience/xcode_logo.png new file mode 100644 index 0000000..73e4da2 Binary files /dev/null and b/Scratch/img/blog/programming-language-experience/xcode_logo.png differ diff --git a/Scratch/img/bullet-feed.png b/Scratch/img/bullet-feed.png new file mode 100644 index 0000000..e6bac9a Binary files /dev/null and b/Scratch/img/bullet-feed.png differ diff --git a/Scratch/img/favicon.ico b/Scratch/img/favicon.ico new file mode 100644 index 0000000..933322a Binary files /dev/null and b/Scratch/img/favicon.ico differ diff --git a/Scratch/img/loading.gif b/Scratch/img/loading.gif new file mode 100644 index 0000000..17ae831 Binary files /dev/null and b/Scratch/img/loading.gif differ diff --git a/Scratch/img/menu/rss-128.png b/Scratch/img/menu/rss-128.png new file mode 100644 index 0000000..c3f5af1 Binary files /dev/null and b/Scratch/img/menu/rss-128.png differ diff --git a/Scratch/img/menu/rss-32.png b/Scratch/img/menu/rss-32.png new file mode 100644 index 0000000..aba9519 Binary files /dev/null and b/Scratch/img/menu/rss-32.png differ diff --git a/Scratch/img/menu/rss-48.png b/Scratch/img/menu/rss-48.png new file mode 100644 index 0000000..855ccaa Binary files /dev/null and b/Scratch/img/menu/rss-48.png differ diff --git a/Scratch/img/menu/rss-64.png b/Scratch/img/menu/rss-64.png new file mode 100644 index 0000000..5fcebe7 Binary files /dev/null and b/Scratch/img/menu/rss-64.png differ diff --git a/Scratch/img/presentation.drawit/Data b/Scratch/img/presentation.drawit/Data new file mode 100644 index 0000000..117607d Binary files /dev/null and b/Scratch/img/presentation.drawit/Data differ diff --git a/Scratch/img/presentation.drawit/Info.plist b/Scratch/img/presentation.drawit/Info.plist new file mode 100644 index 0000000..6e899a6 --- /dev/null +++ b/Scratch/img/presentation.drawit/Info.plist @@ -0,0 +1,8 @@ + + + + + fileVersion + 2 + + diff --git a/Scratch/img/presentation.drawit/QuickLook/Preview.jpg b/Scratch/img/presentation.drawit/QuickLook/Preview.jpg new file mode 100644 index 0000000..15890c9 Binary files /dev/null and b/Scratch/img/presentation.drawit/QuickLook/Preview.jpg differ diff --git a/Scratch/img/presentation.drawit/QuickLook/Thumbnail.jpg b/Scratch/img/presentation.drawit/QuickLook/Thumbnail.jpg new file mode 100644 index 0000000..69869d1 Binary files /dev/null and b/Scratch/img/presentation.drawit/QuickLook/Thumbnail.jpg differ diff --git a/Scratch/img/presentation.png b/Scratch/img/presentation.png new file mode 100644 index 0000000..08d4d20 Binary files /dev/null and b/Scratch/img/presentation.png differ diff --git a/Scratch/img/softwares/yaquabubbles/screenshot1.png b/Scratch/img/softwares/yaquabubbles/screenshot1.png new file mode 100644 index 0000000..bd587d9 Binary files /dev/null and b/Scratch/img/softwares/yaquabubbles/screenshot1.png differ diff --git a/Scratch/img/softwares/yclock/screenshot1.png b/Scratch/img/softwares/yclock/screenshot1.png new file mode 100644 index 0000000..908288d Binary files /dev/null and b/Scratch/img/softwares/yclock/screenshot1.png differ diff --git a/Scratch/js/article.js b/Scratch/js/article.js new file mode 100644 index 0000000..f81df35 --- /dev/null +++ b/Scratch/js/article.js @@ -0,0 +1,3 @@ +$(document).ready( function() { + hljs.initHighlighting(); +}); diff --git a/Scratch/js/become_hidden.html b/Scratch/js/become_hidden.html new file mode 100644 index 0000000..08259ce --- /dev/null +++ b/Scratch/js/become_hidden.html @@ -0,0 +1,33 @@ + + + + + + + + Hide to analytics + + +
    +
    +

    + Hide to Analytics +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/Scratch/js/become_visible.html b/Scratch/js/become_visible.html new file mode 100644 index 0000000..6beafe7 --- /dev/null +++ b/Scratch/js/become_visible.html @@ -0,0 +1,33 @@ + + + + + + + + Be visible to analytics + + +
    +
    +

    + Be visible to Analytics +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/Scratch/js/genericCommentWrapperV2.js b/Scratch/js/genericCommentWrapperV2.js new file mode 100644 index 0000000..bfe5f87 --- /dev/null +++ b/Scratch/js/genericCommentWrapperV2.js @@ -0,0 +1,23 @@ +if(document.getElementById("IDCommentsPostTitle") && document.getElementById("IDCommentsPostTitle").innerHTML.length>0) + idcomments_post_title = document.getElementById("IDCommentsPostTitle").innerHTML; +else + idcomments_post_title = document.title; +if(idcomments_post_title.length==0) + idcomments_post_title = "Post"; + +idcomments_post_title = idcomments_post_title.replace(/#/, "%23"); + +if(null==idcomments_post_url || idcomments_post_url==='') + idcomments_post_url = window.location.href; +if(null==idcomments_post_id) + idcomments_post_id = window.location.href; +idcomments_post_id = encodeURIComponent(idcomments_post_id); +idcomments_post_url = encodeURIComponent(idcomments_post_url); +idcomments_post_title = encodeURIComponent(idcomments_post_title); +var commentScript = document.createElement("script"); +commentScript.type = "text/javascript"; +commentScript.src = "http://intensedebate.com/js/genericCommentWrapper2.php?acct="+idcomments_acct+"&postid="+idcomments_post_id+"&title="+idcomments_post_title+"&url="+idcomments_post_url; + +$(document).ready( function() { + document.getElementsByTagName("head")[0].appendChild(commentScript); +}); diff --git a/Scratch/js/highlight/AUTHORS.en.txt b/Scratch/js/highlight/AUTHORS.en.txt new file mode 100644 index 0000000..6230093 --- /dev/null +++ b/Scratch/js/highlight/AUTHORS.en.txt @@ -0,0 +1,55 @@ +Syntax highlighting with language autodetection. + +URL: http://softwaremaniacs.org/soft/highlight/en/ + +Original author and current maintainer: +Ivan Sagalaev + +Contributors: + +- Peter Leonov +- Victor Karamzin +- Vsevolod Solovyov +- Anton Kovalyov +- Nikita Ledyaev +- Konstantin Evdokimenko +- Dmitri Roudakov +- Yuri Ivanov +- Vladimir Ermakov +- Vladimir Gubarkov +- Brian Beck +- MajestiC +- Vasily Polovnyov +- Vladimir Epifanov +- Alexander Makarov (http://rmcreative.ru/) +- Vah +- Shuen-Huei Guan +- Jason Diamond +- Michal Gabrukiewicz +- Ruslan Keba +- Sergey Baranov +- Zaripov Yura +- Oleg Volchkov +- Vasily Mikhailitchenko +- Jan Berkel +- Vladimir Moskva +- Loren Segal +- Andrew Fedorov +- Igor Kalnitsky +- Jeremy Hull +- Valerii Hiora +- Nikolay Zakharov +- Dmitry Kovega +- Sergey Ignatov +- Antono Vasiljev +- Stephan Kountso +- pumbur +- John Crepezzi +- Andrey Vlasovskikh +- Alexander Myadzel +- Evgeny Stepanischev +- Dmytrii Nagirniak +- Oleg Efimov +- Luigi Maselli +- Denis Bardadym +- Aahan Krish diff --git a/Scratch/js/highlight/AUTHORS.ru.txt b/Scratch/js/highlight/AUTHORS.ru.txt new file mode 100644 index 0000000..d9bfa4b --- /dev/null +++ b/Scratch/js/highlight/AUTHORS.ru.txt @@ -0,0 +1,55 @@ +Подсветка синтаксиса с автоопределением языка. + +URL: http://softwaremaniacs.org/soft/highlight/ + +Первоначальный автор и ведущий проекта: +Иван Сагалаев + +Внесли свой вклад: + +- Петр Леонов +- Виктор Карамзин +- Всеволод Соловьёв +- Антон Ковалёв +- Никита Ледяев +- Константин Евдокименко +- Дмитрий Рудаков +- Юрий Иванов +- Владимир Ермаков +- Владимир Губарьков +- Брайан Бек +- MajestiC +- Василий Половнёв +- Владимир Епифанов +- Александр Макаров (http://rmcreative.ru/) +- Vah +- Шуэн-Хуэй Гуан +- Джейсон Даймонд +- Михал Габрукевич +- Руслан Кеба +- Сергей Баранов +- Зарипов Юра +- Олег Волчков +- Василий Михайличенко +- Ян Беркель +- Владимир Москва +- Лорен Сегал +- Андрей Фёдоров +- Игорь Кальницкий +- Джереми Халл +- Валерий Хиора +- Николай Захаров +- Дмитрий Ковега +- Сергей Игнатов +- Антоно Васильев +- Степан Кунцьо +- pumbur +- Джон Крепецци +- Андрей Власовских +- Александр Мядзель +- Евгений Степанищев +- Дмитрий Нагирняк +- Олег Ефимов +- Луиджи Мазелли +- Денис Бардадым +- Аахан Криш diff --git a/Scratch/js/highlight/LICENSE b/Scratch/js/highlight/LICENSE new file mode 100644 index 0000000..422deb7 --- /dev/null +++ b/Scratch/js/highlight/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2006, Ivan Sagalaev +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of highlight.js nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 THE REGENTS AND 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. diff --git a/Scratch/js/highlight/README.md b/Scratch/js/highlight/README.md new file mode 100644 index 0000000..f8ff5ff --- /dev/null +++ b/Scratch/js/highlight/README.md @@ -0,0 +1,140 @@ +# Highlight.js + +theme: scientific +Highlight.js highlights syntax in code examples on blogs, forums and, +in fact, on any web page. It's very easy to use because it works +automatically: finds blocks of code, detects a language, highlights it. + +Autodetection can be fine tuned when it fails by itself (see "Heuristics"). + + +## Installation and usage + +The download package includes the file "highlight.pack.js" which is a full +compressed version of the library intended for use in production. All +uncompressed source files are also available, feel free to look into them! + +The script is installed by linking to a single file and making a single +initialization call: + +```html + + +``` + +Also you can replace TAB ('\x09') characters used for indentation in your code +with some fixed number of spaces or with a `` to give them special +styling: + +```html + +``` + +The script looks in your page for fragments `
    ...
    ` +that are traditionally used to mark up code examples. Their content is +marked up by logical pieces with defined class names. + + +### Custom initialization + +If you use different markup for code blocks you can initialize them manually +with `highlightBlock(code, tabReplace)` function. It takes a DOM element +containing the code to highlight and optionally a string with which to replace +TAB characters. + +Initialization using, for example, jQuery might look like this: + +```javascript +$(document).ready(function() { + $('pre code').each(function(i, e) {hljs.highlightBlock(e, ' ')}); +}); +``` + +If your code container relies on `
    ` tags instead of line breaks (i.e. if +it's not `
    `) pass `true` into third parameter of `highlightBlock`:
    +
    +```javascript
    +$('div.code').each(function(i, e) {hljs.highlightBlock(e, null, true)});
    +```
    +
    +### Styling
    +
    +Elements of code marked up with classes can be styled as desired:
    +
    +```css
    +.comment {
    +  color: gray;
    +}
    +
    +.keyword {
    +  font-weight: bold;
    +}
    +
    +.python .string {
    +  color: blue;
    +}
    +
    +.html .atribute .value {
    +  color: green;
    +}
    +```
    +
    +Highlight.js comes with several style themes located in "styles" directory that
    +can be used directly or as a base for your own experiments.
    +
    +**Note**: provided styles work for code defined inside `
    ` blocks. If you use
    +custom markup you should modify styles accordingly.
    +
    +For full reference list of classes see [classref.txt][cr].
    +
    +[cr]: http://github.com/isagalaev/highlight.js/blob/master/classref.txt
    +
    +
    +## Export
    +
    +File export.html contains a little program that allows you to paste in a code
    +snippet and then copy and paste the resulting HTML code generated by the
    +highlighter. This is useful in situations when you can't use the script itself
    +on a site.
    +
    +
    +## Heuristics
    +
    +Autodetection of a code's language is done using a simple heuristic:
    +the program tries to highlight a fragment with all available languages and
    +counts all syntactic structures that it finds along the way. The language
    +with greatest count wins.
    +
    +This means that in short fragments the probability of an error is high
    +(and it really happens sometimes). In this cases you can set the fragment's
    +language explicitly by assigning a class to the `` element:
    +
    +```html
    +
    ...
    +``` + +You can use class names recommended in HTML5: "language-html", +"language-php". Classes also can be assigned to the `
    ` element.
    +
    +To disable highlighting of a fragment altogether use "no-highlight" class:
    +
    +```html
    +
    ...
    +``` + +## Meta + +- Version: 6.2 +- URL: http://softwaremaniacs.org/soft/highlight/en/ +- Author: Ivan Sagalaev () + +For the license terms see LICENSE files. +For the list of contributors see AUTHORS.en.txt file. diff --git a/Scratch/js/highlight/README.ru.md b/Scratch/js/highlight/README.ru.md new file mode 100644 index 0000000..8779074 --- /dev/null +++ b/Scratch/js/highlight/README.ru.md @@ -0,0 +1,145 @@ +# Highlight.js + +theme: scientific +Highlight.js нужен для подсветки синтаксиса в примерах кода в блогах, +форумах и вообще на любых веб-страницах. Пользоваться им очень просто, +потому что работает он автоматически: сам находит блоки кода, сам +определяет язык, сам подсвечивает. + +Автоопределением языка можно управлять, когда оно не справляется само (см. +дальше "Эвристика"). + + +## Подключение и использование + +В загруженном архиве лежит файл "highlight.pack.js" -- полная сжатая версия +библиотеки для работы. Все несжатые исходные файлы также есть в пакете, поэтому +не стесняйтесь в них смотреть! + +Скрипт подключается одним файлом и одним вызовом инициализирующей +функции: + +```html + + +``` + +Также вы можете заменить символы TAB ('\x09'), используемые для отступов, на +фиксированное количество пробелов или на отдельный ``, чтобы задать ему +какой-нибудь специальный стиль: + +```html + +``` + +Дальше скрипт ищет на странице конструкции `
    ...
    `, +которые традиционно используются для написания кода, и код в них +размечается на куски, помеченные разными значениями классов. + + +### Инициализация вручную + +Если вы используете другие теги для блоков кода, вы можете инициализировать их +явно с помощью функции `highlightBlock(code, tabReplace)`. Она принимает +DOM-элемент с текстом расцвечиваемого кода и опционально - строчку для замены +символов TAB. + +Например с использованием jQuery код инициализации может выглядеть так: + +```javascript +$(document).ready(function() { + $('pre code').each(function(i, e) {hljs.highlightBlock(e, ' ')}); +}); +``` + +Если ваш блок кода использует `
    ` вместо переводов строки (т.е. если это не +`
    `), передайте `true` третьим параметром в `highlightBlock`:
    +
    +```javascript
    +$('div.code').each(function(i, e) {hljs.highlightBlock(e, null, true)});
    +```
    +
    +### Выбор стилей
    +
    +Размеченным классами элементам кода можно задать желаемые стили например так:
    +
    +```css
    +.comment {
    +  color: gray;
    +}
    +
    +.keyword {
    +  font-weight: bold;
    +}
    +
    +.python .string {
    +  color: blue;
    +}
    +
    +.html .atribute .value {
    +  color: green;
    +}
    +```
    +
    +В комплекте с highlight.js идут несколько стилевых тем в директории styles,
    +которые можно использовать напрямую или как основу для собственных экспериментов.
    +
    +**Внимание**: приложенные стили работают для кода, определённого внутри блоков
    +`
    `. Если вы используете собственную разметку, стили нужно исправить
    +соответствующим образом.
    +
    +Полный список классов приведён в файле [crossref.txt][cr].
    +
    +[cr]: http://github.com/isagalaev/highlight.js/blob/master/classref.txt
    +
    +
    +## Экспорт
    +
    +В файле export.html находится небольшая программка, которая показывает и дает
    +скопировать непосредственно HTML-код подсветки для любого заданного фрагмента кода.
    +Это может понадобится например на сайте, на котором нельзя подключить сам скрипт
    +highlight.js.
    +
    +
    +## Эвристика
    +
    +Определение языка, на котором написан фрагмент, делается с помощью
    +довольно простой эвристики: программа пытается расцветить фрагмент всеми
    +языками подряд, и для каждого языка считает количество подошедших
    +синтаксически конструкций и ключевых слов. Для какого языка нашлось больше,
    +тот и выбирается.
    +
    +Это означает, что в коротких фрагментах высока вероятность ошибки, что
    +периодически и случается. Чтобы указать язык фрагмента явно, надо написать
    +его название в виде класса к элементу ``:
    +
    +```html
    +
    ...
    +``` + +Можно использовать рекомендованные в HTML5 названия классов: +"language-html", "language-php". Также можно назначать классы на элемент +`
    `.
    +
    +Чтобы запретить расцветку фрагмента вообще, используется класс "no-highlight":
    +
    +```html
    +
    ...
    +``` + +## Координаты + +- Версия: 6.2 +- URL: http://softwaremaniacs.org/soft/highlight/ +- Автор: Иван Сагалаев () + +Лицензионное соглашение читайте в файле LICENSE. +Список соавторов читайте в файле AUTHORS.ru.txt diff --git a/Scratch/js/highlight/classref.txt b/Scratch/js/highlight/classref.txt new file mode 100644 index 0000000..b7707f4 --- /dev/null +++ b/Scratch/js/highlight/classref.txt @@ -0,0 +1,497 @@ +This is a full list of available classes corresponding to languages' +syntactic structures. The parentheses after language name contain identifiers +used as class names in `` element. + +Python ("python"): + + keyword keyword + built_in built-in objects (None, False, True and Ellipsis) + number number + string string (of any type) + comment comment + decorator @-decorator for functions + function function header "def some_name(...):" + class class header "class SomeName(...):" + title name of a function or a class inside a header + params everything inside parentheses in a function's or class' header + +Python profiler results ("profile"): + + number number + string string + builtin builtin function entry + filename filename in an entry + summary profiling summary + header header of table of results + keyword column header + function function name in an entry (including parentheses) + title actual name of a function in an entry (excluding parentheses) + +Ruby ("ruby"): + + keyword keyword + string string + subst in-string substitution (#{...}) + comment comment + yardoctag YARD tag + function function header "def some_name(...):" + class class header "class SomeName(...):" + title name of a function or a class inside a header + parent name of a parent class + symbol symbol + instancevar instance variable + +Perl ("perl"): + + keyword keyword + comment comment + number number + string string + regexp regular expression + sub subroutine header (from "sub" till "{") + variable variable starting with "$", "%", "@" + operator operator + pod plain old doc + +PHP ("php"): + + keyword keyword + number number + string string (of any type) + comment comment + phpdoc phpdoc params in comments + variable variable starting with "$" + preprocessor preprocessor marks: "" + +Scala ("scala"): + + keyword keyword + number number + string string + comment comment + annotaion annotation + javadoc javadoc comment + javadoctag @-tag in javadoc + class class header + title class name inside a header + params everything in parentheses inside a class header + inheritance keywords "extends" and "with" inside class header + +Go language ("go"): + comment comment + string string constant + number number + keyword language keywords + constant true false nil iota + typename built-in plain types (int, string etc.) + built_in built-in functions + +XML ("xml"): + + tag any tag from "<" till ">" + comment comment + pi processing instruction () + cdata CDATA section + attribute attribute + value attribute's value + +HTML ("html"): + + keyword HTML tag + tag any tag from "<" till ">" + comment comment + doctype declaration + attribute tag's attribute with or without value + value attribute's value + +CSS ("css"): + + tag HTML tag in selectors + id #some_name in selectors + class .some_name in selectors + at_rule @-rule till first "{" or ";" + attr_selector attribute selector (square brackets in a[href^=http://]) + pseudo pseudo classes and elemens (:after, ::after etc.) + comment comment + rules everything from "{" till "}" + property property name inside a rule + value property value inside a rule, from ":" till ";" or + till the end of rule block + number number within a value + string string within a value + hexcolor hex color (#FFFFFF) within a value + function CSS function within a value + params everything between "(" and ")" within a function + important "!important" symbol + +Markdown ("markdown"): + + header header + bullet list bullet + emphasis emphasis + strong strong emphasis + blockquote blockquote + code code + horizontal_rule horizontal rule + link_label link label + link_url link url + +Django ("django"): + + keyword HTML tag in HTML, default tags and default filters in templates + tag any tag from "<" till ">" + comment comment + doctype declaration + attribute tag's attribute with or withou value + value attribute's value + template_tag template tag {% .. %} + variable template variable {{ .. }} + template_comment template comment, both {# .. #} and {% comment %} + filter filter from "|" till the next filter or the end of tag + argument filter argument + +JavaScript ("javascript"): + + keyword keyword + comment comment + number number + literal special literal: "true", "false" and "null" + string string + regexp regular expression + function header of a function + title name of a function inside a header + params parentheses and everything inside them in a function's header + +CoffeeScript ("coffeescript"): + + keyword keyword + comment comment + number number + literal special literal: "true", "false" and "null" + string string + regexp regular expression + function header of a function + title name of a function variable inside a header + params parentheses and everything inside them in a function's header + +VBScript ("vbscript"): + + keyword keyword + number number + string string + comment comment + built_in built-in function + +Lua ("lua"): + + keyword keyword + number number + string string + comment comment + built_in built-in operator + function header of a function + title name of a function inside a header + params everything inside parentheses in a function's header + long_brackets multiline string in [=[ .. ]=] + +Delphi ("delphi"): + + keyword keyword + comment comment (of any type) + number number + string string + function header of a function, procedure, constructor and destructor + title name of a function, procedure, constructor or destructor + inside a header + params everything inside parentheses in a function's header + class class' body from "= class" till "end;" + +Java ("java"): + + keyword keyword + number number + string string + comment commment + annotaion annotation + javadoc javadoc comment + class class header from "class" till "{" + title class name inside a header + params everything in parentheses inside a class header + inheritance keywords "extends" and "implements" inside class header + +C++ ("cpp"): + + keyword keyword + number number + string string and character + comment comment + preprocessor preprocessor directive + stl_container instantiation of STL containers ("vector<...>") + +Objective C ("objectivec"): + keyword keyword + built_in Cocoa/Cocoa Touch constants and classes + number number + string string + comment comment + preprocessor preprocessor directive + class interface/implementation, protocol and forward class declaration + +Vala ("vala"): + + keyword keyword + number number + string string + comment comment + class class definitions + title in class definition + constant ALL_UPPER_CASE + +C# ("cs"): + + keyword keyword + number number + string string + comment commment + xmlDocTag xmldoc tag ("///", "", "<..>") + +RenderMan RSL ("rsl"): + + keyword keyword + number number + string string (including @"..") + comment comment + preprocessor preprocessor directive + shader sahder keywords + shading shading keywords + built_in built-in function + +RenderMan RIB ("rib"): + + keyword keyword + number number + string string + comment comment + commands command + +Maya Embedded Language ("mel"): + + keyword keyword + number number + string string + comment comment + variable variable + +SQL ("sql"): + + keyword keyword (mostly SQL'92 and SQL'99) + number number + string string (of any type: "..", '..', `..`) + comment comment + aggregate aggregate function + +Smalltalk ("smalltalk"): + + keyword keyword + number number + string string + comment commment + symbol symbol + array array + class name of a class + char char + localvars block of local variables + +Lisp ("lisp"): + + keyword keyword + number number + string string + comment commment + variable variable + literal b, t and nil + list non-quoted list + title first symbol in a non-quoted list + body remainder of the non-quoted list + quoted quoted list, both "(quote .. )" and "'(..)" + +Ini ("ini"): + + title title of a section + value value of a setting of any type + string string + number number + keyword boolean value keyword + +Apache ("apache"): + + keyword keyword + number number + comment commment + literal On and Off + sqbracket variables in rewrites "%{..}" + cbracket options in rewrites "[..]" + tag begin and end of a configuration section + +Nginx ("nginx"): + + keyword keyword + string string + number number + comment comment + built_in built-in constant + variable $-variable + +Diff ("diff"): + + header file header + chunk chunk header within a file + addition added lines + deletion deleted lines + change changed lines + +DOS ("dos"): + + keyword keyword + flow batch control keyword + stream DOS special files ("con", "prn", ...) + winutils some commands (see dos.js specifically) + envvar environment variables + +Bash ("bash"): + + keyword keyword + string string + number number + comment comment + literal special literal: "true" и "false" + variable variable + shebang script interpreter header + +CMake ("cmake") + + keyword keyword + number number + string string + comment commment + envvar $-variable + +Axapta ("axapta"): + + keyword keyword + number number + string string + comment commment + class class header from "class" till "{" + title class name inside a header + params everything in parentheses inside a class header + inheritance keywords "extends" and "implements" inside class header + preprocessor preprocessor directive + +1C ("1c"): + + keyword keyword + number number + date date + string string + comment commment + function header of function or procudure + title function name inside a header + params everything in parentheses inside a function header + preprocessor preprocessor directive + +AVR assembler ("avrasm"): + + keyword keyword + built_in pre-defined register + number number + string string + comment commment + label label + preprocessor preprocessor directive + localvars substitution in .macro + +VHDL ("vhdl") + + keyword keyword + number number + string string + comment commment + literal signal logical value + +Parser3 ("parser3"): + + keyword keyword + number number + comment commment + variable variable starting with "$" + preprocessor preprocessor directive + title user-defined name starting with "@" + +TeX ("tex"): + + comment comment + number number + command command + parameter parameter + formula formula + special special symbol + +Haskell ("haskell"): + + keyword keyword + built_in built-in typeclass/functions (Bool, Int) + number number + string string + comment comment + class type classes and other data types + title function name + label type class name + +Erlang ("erlang"): + + comment comment + string string + number number + keyword keyword + record_name record access (#record_name) + title name of declaration function + variable variable (starts with capital letter or with _) + pp.keywords module's attribute (-attribute) + function_name atom or atom:atom in case of function call + +Rust ("rust"): + + comment comment + string string + number number + keyword keyword + title name of declaration + preprocessor preprocessor directive + +ActionScript ("actionscript"): + + comment comment + string string + number number + keyword keywords + literal literal + reserved reserved keyword + title name of declaration (package, class or function) + preprocessor preprocessor directive (import, include) + type type of returned value (for functions) + package package (named or not) + class class/interface + function function + param params of function + rest_arg rest argument of function + +Matlab ("matlab"): + + comment comment + string string + number number + keyword keyword + title function name + function function + param params of function diff --git a/Scratch/js/highlight/export.html b/Scratch/js/highlight/export.html new file mode 100644 index 0000000..6328f7e --- /dev/null +++ b/Scratch/js/highlight/export.html @@ -0,0 +1,88 @@ + + + + + + + + Highlited code export + + + + + + + + + + + + + + + + + + +
    Write a code snippetGet HTML to paste anywhere (for actual styles and colors see sample.css)
    + + + + + +
    +
    +
    + Export script: Vladimir Gubarkov
    + Highlighting: highlight.js +
    + + diff --git a/Scratch/js/highlight/highlight.js b/Scratch/js/highlight/highlight.js new file mode 100644 index 0000000..72fbc3a --- /dev/null +++ b/Scratch/js/highlight/highlight.js @@ -0,0 +1,643 @@ +/* +Syntax highlighting with language autodetection. +http://softwaremaniacs.org/soft/highlight/ +*/ + +var hljs = new function() { + + /* Utility functions */ + + function escape(value) { + return value.replace(/&/gm, '&').replace(/'; + } + + while (stream1.length || stream2.length) { + var current = selectStream().splice(0, 1)[0]; + result += escape(value.substr(processed, current.offset - processed)); + processed = current.offset; + if ( current.event == 'start') { + result += open(current.node); + nodeStack.push(current.node); + } else if (current.event == 'stop') { + var node, i = nodeStack.length; + do { + i--; + node = nodeStack[i]; + result += (''); + } while (node != current.node); + nodeStack.splice(i, 1); + while (i < nodeStack.length) { + result += open(nodeStack[i]); + i++; + } + } + } + return result + escape(value.substr(processed)); + } + + /* Initialization */ + + function compileModes() { + + function compileMode(mode, language, is_default) { + if (mode.compiled) + return; + var group; + + if (!is_default) { + mode.beginRe = langRe(language, mode.begin ? mode.begin : '\\B|\\b'); + if (!mode.end && !mode.endsWithParent) + mode.end = '\\B|\\b'; + if (mode.end) + mode.endRe = langRe(language, mode.end); + } + if (mode.illegal) + mode.illegalRe = langRe(language, mode.illegal); + if (mode.relevance === undefined) + mode.relevance = 1; + if (mode.keywords) { + mode.lexemsRe = langRe(language, mode.lexems || hljs.IDENT_RE, true); + for (var className in mode.keywords) { + if (!mode.keywords.hasOwnProperty(className)) + continue; + if (mode.keywords[className] instanceof Object) { + group = mode.keywords[className]; + } else { + group = mode.keywords; + className = 'keyword'; + } + for (var keyword in group) { + if (!group.hasOwnProperty(keyword)) + continue; + mode.keywords[keyword] = [className, group[keyword]]; + } + } + } + if (!mode.contains) { + mode.contains = []; + } + // compiled flag is set before compiling submodes to avoid self-recursion + // (see lisp where quoted_list contains quoted_list) + mode.compiled = true; + for (var i = 0; i < mode.contains.length; i++) { + if (mode.contains[i] == 'self') { + mode.contains[i] = mode; + } + compileMode(mode.contains[i], language, false); + } + if (mode.starts) { + compileMode(mode.starts, language, false); + } + } + + for (var i in languages) { + if (!languages.hasOwnProperty(i)) + continue; + compileMode(languages[i].defaultMode, languages[i], true); + } + } + + /* + Core highlighting function. Accepts a language name and a string with the + code to highlight. Returns an object with the following properties: + + - relevance (int) + - keyword_count (int) + - value (an HTML string with highlighting markup) + + */ + function highlight(language_name, value) { + if (!compileModes.called) { + compileModes(); + compileModes.called = true; + } + + function subMode(lexem, mode) { + for (var i = 0; i < mode.contains.length; i++) { + if (mode.contains[i].beginRe.test(lexem)) { + return mode.contains[i]; + } + } + } + + function endOfMode(mode_index, lexem) { + if (modes[mode_index].end && modes[mode_index].endRe.test(lexem)) + return 1; + if (modes[mode_index].endsWithParent) { + var level = endOfMode(mode_index - 1, lexem); + return level ? level + 1 : 0; + } + return 0; + } + + function isIllegal(lexem, mode) { + return mode.illegal && mode.illegalRe.test(lexem); + } + + function compileTerminators(mode, language) { + var terminators = []; + + for (var i = 0; i < mode.contains.length; i++) { + terminators.push(mode.contains[i].begin); + } + + var index = modes.length - 1; + do { + if (modes[index].end) { + terminators.push(modes[index].end); + } + index--; + } while (modes[index + 1].endsWithParent); + + if (mode.illegal) { + terminators.push(mode.illegal); + } + + return langRe(language, '(' + terminators.join('|') + ')', true); + } + + function eatModeChunk(value, index) { + var mode = modes[modes.length - 1]; + if (!mode.terminators) { + mode.terminators = compileTerminators(mode, language); + } + mode.terminators.lastIndex = index; + var match = mode.terminators.exec(value); + if (match) + return [value.substr(index, match.index - index), match[0], false]; + else + return [value.substr(index), '', true]; + } + + function keywordMatch(mode, match) { + var match_str = language.case_insensitive ? match[0].toLowerCase() : match[0]; + var value = mode.keywords[match_str]; + if (value && value instanceof Array) + return value; + return false; + } + + function processKeywords(buffer, mode) { + buffer = escape(buffer); + if (!mode.keywords) + return buffer; + var result = ''; + var last_index = 0; + mode.lexemsRe.lastIndex = 0; + var match = mode.lexemsRe.exec(buffer); + while (match) { + result += buffer.substr(last_index, match.index - last_index); + var keyword_match = keywordMatch(mode, match); + if (keyword_match) { + keyword_count += keyword_match[1]; + result += '' + match[0] + ''; + } else { + result += match[0]; + } + last_index = mode.lexemsRe.lastIndex; + match = mode.lexemsRe.exec(buffer); + } + return result + buffer.substr(last_index, buffer.length - last_index); + } + + function processBuffer(buffer, mode) { + if (mode.subLanguage && languages[mode.subLanguage]) { + var result = highlight(mode.subLanguage, buffer); + keyword_count += result.keyword_count; + return result.value; + } else { + return processKeywords(buffer, mode); + } + } + + function startNewMode(mode, lexem) { + var markup = mode.className?'':''; + if (mode.returnBegin) { + result += markup; + mode.buffer = ''; + } else if (mode.excludeBegin) { + result += escape(lexem) + markup; + mode.buffer = ''; + } else { + result += markup; + mode.buffer = lexem; + } + modes.push(mode); + relevance += mode.relevance; + } + + function processModeInfo(buffer, lexem, end) { + var current_mode = modes[modes.length - 1]; + if (end) { + result += processBuffer(current_mode.buffer + buffer, current_mode); + return false; + } + + var new_mode = subMode(lexem, current_mode); + if (new_mode) { + result += processBuffer(current_mode.buffer + buffer, current_mode); + startNewMode(new_mode, lexem); + return new_mode.returnBegin; + } + + var end_level = endOfMode(modes.length - 1, lexem); + if (end_level) { + var markup = current_mode.className?'':''; + if (current_mode.returnEnd) { + result += processBuffer(current_mode.buffer + buffer, current_mode) + markup; + } else if (current_mode.excludeEnd) { + result += processBuffer(current_mode.buffer + buffer, current_mode) + markup + escape(lexem); + } else { + result += processBuffer(current_mode.buffer + buffer + lexem, current_mode) + markup; + } + while (end_level > 1) { + markup = modes[modes.length - 2].className?'':''; + result += markup; + end_level--; + modes.length--; + } + var last_ended_mode = modes[modes.length - 1]; + modes.length--; + modes[modes.length - 1].buffer = ''; + if (last_ended_mode.starts) { + startNewMode(last_ended_mode.starts, ''); + } + return current_mode.returnEnd; + } + + if (isIllegal(lexem, current_mode)) + throw 'Illegal'; + } + + var language = languages[language_name]; + var modes = [language.defaultMode]; + var relevance = 0; + var keyword_count = 0; + var result = ''; + try { + var mode_info, index = 0; + language.defaultMode.buffer = ''; + do { + mode_info = eatModeChunk(value, index); + var return_lexem = processModeInfo(mode_info[0], mode_info[1], mode_info[2]); + index += mode_info[0].length; + if (!return_lexem) { + index += mode_info[1].length; + } + } while (!mode_info[2]); + if(modes.length > 1) + throw 'Illegal'; + return { + relevance: relevance, + keyword_count: keyword_count, + value: result + }; + } catch (e) { + if (e == 'Illegal') { + return { + relevance: 0, + keyword_count: 0, + value: escape(value) + }; + } else { + throw e; + } + } + } + + /* + Highlighting with language detection. Accepts a string with the code to + highlight. Returns an object with the following properties: + + - language (detected language) + - relevance (int) + - keyword_count (int) + - value (an HTML string with highlighting markup) + - second_best (object with the same structure for second-best heuristically + detected language, may be absent) + + */ + function highlightAuto(text) { + var result = { + keyword_count: 0, + relevance: 0, + value: escape(text) + }; + var second_best = result; + for (var key in languages) { + if (!languages.hasOwnProperty(key)) + continue; + var current = highlight(key, text); + current.language = key; + if (current.keyword_count + current.relevance > second_best.keyword_count + second_best.relevance) { + second_best = current; + } + if (current.keyword_count + current.relevance > result.keyword_count + result.relevance) { + second_best = result; + result = current; + } + } + if (second_best.language) { + result.second_best = second_best; + } + return result; + } + + /* + Post-processing of the highlighted markup: + + - replace TABs with something more useful + - replace real line-breaks with '
    ' for non-pre containers + + */ + function fixMarkup(value, tabReplace, useBR) { + if (tabReplace) { + value = value.replace(/^((<[^>]+>|\t)+)/gm, function(match, p1, offset, s) { + return p1.replace(/\t/g, tabReplace); + }); + } + if (useBR) { + value = value.replace(/\n/g, '
    '); + } + return value; + } + + /* + Applies highlighting to a DOM node containing code. Accepts a DOM node and + two optional parameters for fixMarkup. + */ + function highlightBlock(block, tabReplace, useBR) { + var text = blockText(block, useBR); + var language = blockLanguage(block); + var result, pre; + if (language == 'no-highlight') + return; + if (language) { + result = highlight(language, text); + } else { + result = highlightAuto(text); + language = result.language; + } + var original = nodeStream(block); + if (original.length) { + pre = document.createElement('pre'); + pre.innerHTML = result.value; + result.value = mergeStreams(original, nodeStream(pre), text); + } + result.value = fixMarkup(result.value, tabReplace, useBR); + + var class_name = block.className; + if (!class_name.match('(\\s|^)(language-)?' + language + '(\\s|$)')) { + class_name = class_name ? (class_name + ' ' + language) : language; + } + if (/MSIE [678]/.test(navigator.userAgent) && block.tagName == 'CODE' && block.parentNode.tagName == 'PRE') { + // This is for backwards compatibility only. IE needs this strange + // hack becasue it cannot just cleanly replace block contents. + pre = block.parentNode; + var container = document.createElement('div'); + container.innerHTML = '
    ' + result.value + '
    '; + block = container.firstChild.firstChild; + container.firstChild.className = pre.className; + pre.parentNode.replaceChild(container.firstChild, pre); + } else { + block.innerHTML = result.value; + } + block.className = class_name; + block.result = { + language: language, + kw: result.keyword_count, + re: result.relevance + }; + if (result.second_best) { + block.second_best = { + language: result.second_best.language, + kw: result.second_best.keyword_count, + re: result.second_best.relevance + }; + } + } + + /* + Applies highlighting to all
    ..
    blocks on a page. + */ + function initHighlighting() { + if (initHighlighting.called) + return; + initHighlighting.called = true; + var pres = document.getElementsByTagName('pre'); + for (var i = 0; i < pres.length; i++) { + var code = findCode(pres[i]); + if (code) + highlightBlock(code, hljs.tabReplace); + } + } + + /* + Attaches highlighting to the page load event. + */ + function initHighlightingOnLoad() { + if (window.addEventListener) { + window.addEventListener('DOMContentLoaded', initHighlighting, false); + window.addEventListener('load', initHighlighting, false); + } else if (window.attachEvent) + window.attachEvent('onload', initHighlighting); + else + window.onload = initHighlighting; + } + + var languages = {}; // a shortcut to avoid writing "this." everywhere + + /* Interface definition */ + + this.LANGUAGES = languages; + this.highlight = highlight; + this.highlightAuto = highlightAuto; + this.fixMarkup = fixMarkup; + this.highlightBlock = highlightBlock; + this.initHighlighting = initHighlighting; + this.initHighlightingOnLoad = initHighlightingOnLoad; + + // Common regexps + this.IDENT_RE = '[a-zA-Z][a-zA-Z0-9_]*'; + this.UNDERSCORE_IDENT_RE = '[a-zA-Z_][a-zA-Z0-9_]*'; + this.NUMBER_RE = '\\b\\d+(\\.\\d+)?'; + this.C_NUMBER_RE = '\\b(0[xX][a-fA-F0-9]+|(\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float + this.BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b... + this.RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~'; + + // Common modes + this.BACKSLASH_ESCAPE = { + begin: '\\\\.', relevance: 0 + }; + this.APOS_STRING_MODE = { + className: 'string', + begin: '\'', end: '\'', + illegal: '\\n', + contains: [this.BACKSLASH_ESCAPE], + relevance: 0 + }; + this.QUOTE_STRING_MODE = { + className: 'string', + begin: '"', end: '"', + illegal: '\\n', + contains: [this.BACKSLASH_ESCAPE], + relevance: 0 + }; + this.C_LINE_COMMENT_MODE = { + className: 'comment', + begin: '//', end: '$' + }; + this.C_BLOCK_COMMENT_MODE = { + className: 'comment', + begin: '/\\*', end: '\\*/' + }; + this.HASH_COMMENT_MODE = { + className: 'comment', + begin: '#', end: '$' + }; + this.NUMBER_MODE = { + className: 'number', + begin: this.NUMBER_RE, + relevance: 0 + }; + this.C_NUMBER_MODE = { + className: 'number', + begin: this.C_NUMBER_RE, + relevance: 0 + }; + this.BINARY_NUMBER_MODE = { + className: 'number', + begin: this.BINARY_NUMBER_RE, + relevance: 0 + }; + + // Utility functions + this.inherit = function(parent, obj) { + var result = {} + for (var key in parent) + result[key] = parent[key]; + if (obj) + for (var key in obj) + result[key] = obj[key]; + return result; + } +}(); diff --git a/Scratch/js/highlight/highlight.pack.js b/Scratch/js/highlight/highlight.pack.js new file mode 100644 index 0000000..b0b5859 --- /dev/null +++ b/Scratch/js/highlight/highlight.pack.js @@ -0,0 +1 @@ +var hljs=new function(){function m(p){return p.replace(/&/gm,"&").replace(/"}while(y.length||z.length){var v=u().splice(0,1)[0];w+=m(x.substr(r,v.offset-r));r=v.offset;if(v.event=="start"){w+=s(v.node);t.push(v.node)}else{if(v.event=="stop"){var p,q=t.length;do{q--;p=t[q];w+=("")}while(p!=v.node);t.splice(q,1);while(q'+L[0]+""}else{N+=L[0]}P=O.lR.lastIndex;L=O.lR.exec(M)}return N+M.substr(P,M.length-P)}function K(r,M){if(M.sL&&d[M.sL]){var L=e(M.sL,r);t+=L.keyword_count;return L.value}else{return F(r,M)}}function I(M,r){var L=M.cN?'':"";if(M.rB){q+=L;M.buffer=""}else{if(M.eB){q+=m(r)+L;M.buffer=""}else{q+=L;M.buffer=r}}C.push(M);B+=M.r}function E(O,L,Q){var R=C[C.length-1];if(Q){q+=K(R.buffer+O,R);return false}var M=z(L,R);if(M){q+=K(R.buffer+O,R);I(M,L);return M.rB}var r=w(C.length-1,L);if(r){var N=R.cN?"":"";if(R.rE){q+=K(R.buffer+O,R)+N}else{if(R.eE){q+=K(R.buffer+O,R)+N+m(L)}else{q+=K(R.buffer+O+L,R)+N}}while(r>1){N=C[C.length-2].cN?"":"";q+=N;r--;C.length--}var P=C[C.length-1];C.length--;C[C.length-1].buffer="";if(P.starts){I(P.starts,"")}return R.rE}if(x(L,R)){throw"Illegal"}}var H=d[J];var C=[H.dM];var B=0;var t=0;var q="";try{var y,v=0;H.dM.buffer="";do{y=s(D,v);var u=E(y[0],y[1],y[2]);v+=y[0].length;if(!u){v+=y[1].length}}while(!y[2]);if(C.length>1){throw"Illegal"}return{r:B,keyword_count:t,value:q}}catch(G){if(G=="Illegal"){return{r:0,keyword_count:0,value:m(D)}}else{throw G}}}function f(t){var r={keyword_count:0,r:0,value:m(t)};var q=r;for(var p in d){if(!d.hasOwnProperty(p)){continue}var s=e(p,t);s.language=p;if(s.keyword_count+s.r>q.keyword_count+q.r){q=s}if(s.keyword_count+s.r>r.keyword_count+r.r){q=r;r=s}}if(q.language){r.second_best=q}return r}function h(r,q,p){if(q){r=r.replace(/^((<[^>]+>|\t)+)/gm,function(t,w,v,u){return w.replace(/\t/g,q)})}if(p){r=r.replace(/\n/g,"
    ")}return r}function o(u,x,q){var y=g(u,q);var s=a(u);var w,r;if(s=="no-highlight"){return}if(s){w=e(s,y)}else{w=f(y);s=w.language}var p=b(u);if(p.length){r=document.createElement("pre");r.innerHTML=w.value;w.value=l(p,b(r),y)}w.value=h(w.value,x,q);var t=u.className;if(!t.match("(\\s|^)(language-)?"+s+"(\\s|$)")){t=t?(t+" "+s):s}if(/MSIE [678]/.test(navigator.userAgent)&&u.tagName=="CODE"&&u.parentNode.tagName=="PRE"){r=u.parentNode;var v=document.createElement("div");v.innerHTML="
    "+w.value+"
    ";u=v.firstChild.firstChild;v.firstChild.cN=r.cN;r.parentNode.replaceChild(v.firstChild,r)}else{u.innerHTML=w.value}u.className=t;u.result={language:s,kw:w.keyword_count,re:w.r};if(w.second_best){u.second_best={language:w.second_best.language,kw:w.second_best.keyword_count,re:w.second_best.r}}}function k(){if(k.called){return}k.called=true;var r=document.getElementsByTagName("pre");for(var p=0;p|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\.",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BINARY_NUMBER_MODE={cN:"number",b:this.BINARY_NUMBER_RE,r:0};this.inherit=function(p,s){var r={};for(var q in p){r[q]=p[q]}if(s){for(var q in s){r[q]=s[q]}}return r}}();hljs.LANGUAGES.bash=function(){var d={"true":1,"false":1};var c={cN:"variable",b:"\\$([a-zA-Z0-9_]+)\\b"};var a={cN:"variable",b:"\\$\\{(([^}])|(\\\\}))+\\}",c:[hljs.CNM]};var f={cN:"string",b:'"',e:'"',i:"\\n",c:[hljs.BE,c,a],r:0};var b={cN:"string",b:"'",e:"'",r:0};var e={cN:"test_condition",b:"",e:"",c:[f,b,c,a,hljs.CNM],k:{literal:d},r:0};return{dM:{k:{keyword:{"if":1,then:1,"else":1,fi:1,"for":1,"break":1,"continue":1,"while":1,"in":1,"do":1,done:1,echo:1,exit:1,"return":1,set:1,declare:1},literal:d},c:[{cN:"shebang",b:"(#!\\/bin\\/bash)|(#!\\/bin\\/sh)",r:10},c,a,hljs.HCM,hljs.CNM,f,b,hljs.inherit(e,{b:"\\[ ",e:" \\]",r:0}),hljs.inherit(e,{b:"\\[\\[ ",e:" \\]\\]"})]}}}();hljs.LANGUAGES.erlang=function(){var h="[a-z'][a-zA-Z0-9_']*";var m="("+h+":"+h+"|"+h+")";var d={keyword:{after:1,and:1,andalso:10,band:1,begin:1,bnot:1,bor:1,bsl:1,bzr:1,bxor:1,"case":1,"catch":1,cond:1,div:1,end:1,fun:1,let:1,not:1,of:1,orelse:10,query:1,receive:1,rem:1,"try":1,when:1,xor:1},literal:{"false":1,"true":1}};var k={cN:"comment",b:"%",e:"$",r:0};var f={cN:"number",b:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",r:0};var c={b:"fun\\s+"+h+"/\\d+"};var n={b:m+"\\(",e:"\\)",rB:true,r:0,c:[{cN:"function_name",b:m,r:0},{b:"\\(",e:"\\)",eW:true,rE:true,r:0}]};var g={cN:"tuple",b:"{",e:"}",r:0};var a={cN:"variable",b:"\\b_([A-Z][A-Za-z0-9_]*)?",r:0};var l={cN:"variable",b:"[A-Z][a-zA-Z0-9_]*",r:0};var i={b:"#",e:"}",i:".",r:0,rB:true,c:[{cN:"record_name",b:"#"+hljs.UIR,r:0},{b:"{",eW:true,r:0}]};var j={k:d,b:"(fun|receive|if|try|case)",e:"end"};j.c=[k,c,hljs.inherit(hljs.ASM,{cN:""}),j,n,hljs.QSM,f,g,a,l,i];var b=[k,c,j,n,hljs.QSM,f,g,a,l,i];n.c[1].c=b;g.c=b;i.c[1].c=b;var e={cN:"params",b:"\\(",e:"\\)",eW:true,c:b};return{dM:{k:d,i:"(",eW:true,c:b}]},k,{cN:"pp",b:"^-",e:"\\.",r:0,eE:true,rB:true,l:"-"+hljs.IR,k:{"-module":1,"-record":1,"-undef":1,"-export":1,"-ifdef":1,"-ifndef":1,"-author":1,"-copyright":1,"-doc":1,"-vsn":1,"-import":1,"-include":1,"-include_lib":1,"-compile":1,"-define":1,"-else":1,"-endif":1,"-file":1,"-behaviour":1,"-behavior":1},c:[e]},f,hljs.QSM,i,a,l,g]}}}();hljs.LANGUAGES.cs={dM:{k:{"abstract":1,as:1,base:1,bool:1,"break":1,"byte":1,"case":1,"catch":1,"char":1,checked:1,"class":1,"const":1,"continue":1,decimal:1,"default":1,delegate:1,"do":1,"double":1,"else":1,"enum":1,event:1,explicit:1,extern:1,"false":1,"finally":1,fixed:1,"float":1,"for":1,foreach:1,"goto":1,"if":1,implicit:1,"in":1,"int":1,"interface":1,internal:1,is:1,lock:1,"long":1,namespace:1,"new":1,"null":1,object:1,operator:1,out:1,override:1,params:1,"private":1,"protected":1,"public":1,readonly:1,ref:1,"return":1,sbyte:1,sealed:1,"short":1,sizeof:1,stackalloc:1,"static":1,string:1,struct:1,"switch":1,"this":1,"throw":1,"true":1,"try":1,"typeof":1,uint:1,ulong:1,unchecked:1,unsafe:1,ushort:1,using:1,virtual:1,"volatile":1,"void":1,"while":1,ascending:1,descending:1,from:1,get:1,group:1,into:1,join:1,let:1,orderby:1,partial:1,select:1,set:1,value:1,"var":1,where:1,yield:1},c:[{cN:"comment",b:"///",e:"$",rB:true,c:[{cN:"xmlDocTag",b:"///|"},{cN:"xmlDocTag",b:""}]},hljs.CLCM,hljs.CBLCLM,{cN:"preprocessor",b:"#",e:"$",k:{"if":1,"else":1,elif:1,endif:1,define:1,undef:1,warning:1,error:1,line:1,region:1,endregion:1,pragma:1,checksum:1}},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},hljs.ASM,hljs.QSM,hljs.CNM]}};hljs.LANGUAGES.ruby=function(){var c="[a-zA-Z_][a-zA-Z0-9_]*(\\!|\\?)?";var i="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?";var a={keyword:{and:1,"false":1,then:1,defined:1,module:1,"in":1,"return":1,redo:1,"if":1,BEGIN:1,retry:1,end:1,"for":1,"true":1,self:1,when:1,next:1,until:1,"do":1,begin:1,unless:1,END:1,rescue:1,nil:1,"else":1,"break":1,undef:1,not:1,"super":1,"class":1,"case":1,require:1,yield:1,alias:1,"while":1,ensure:1,elsif:1,or:1,def:1},keymethods:{__id__:1,__send__:1,abort:1,abs:1,"all?":1,allocate:1,ancestors:1,"any?":1,arity:1,assoc:1,at:1,at_exit:1,autoload:1,"autoload?":1,"between?":1,binding:1,binmode:1,"block_given?":1,call:1,callcc:1,caller:1,capitalize:1,"capitalize!":1,casecmp:1,"catch":1,ceil:1,center:1,chomp:1,"chomp!":1,chop:1,"chop!":1,chr:1,"class":1,class_eval:1,"class_variable_defined?":1,class_variables:1,clear:1,clone:1,close:1,close_read:1,close_write:1,"closed?":1,coerce:1,collect:1,"collect!":1,compact:1,"compact!":1,concat:1,"const_defined?":1,const_get:1,const_missing:1,const_set:1,constants:1,count:1,crypt:1,"default":1,default_proc:1,"delete":1,"delete!":1,delete_at:1,delete_if:1,detect:1,display:1,div:1,divmod:1,downcase:1,"downcase!":1,downto:1,dump:1,dup:1,each:1,each_byte:1,each_index:1,each_key:1,each_line:1,each_pair:1,each_value:1,each_with_index:1,"empty?":1,entries:1,eof:1,"eof?":1,"eql?":1,"equal?":1,"eval":1,exec:1,exit:1,"exit!":1,extend:1,fail:1,fcntl:1,fetch:1,fileno:1,fill:1,find:1,find_all:1,first:1,flatten:1,"flatten!":1,floor:1,flush:1,for_fd:1,foreach:1,fork:1,format:1,freeze:1,"frozen?":1,fsync:1,getc:1,gets:1,global_variables:1,grep:1,gsub:1,"gsub!":1,"has_key?":1,"has_value?":1,hash:1,hex:1,id:1,include:1,"include?":1,included_modules:1,index:1,indexes:1,indices:1,induced_from:1,inject:1,insert:1,inspect:1,instance_eval:1,instance_method:1,instance_methods:1,"instance_of?":1,"instance_variable_defined?":1,instance_variable_get:1,instance_variable_set:1,instance_variables:1,"integer?":1,intern:1,invert:1,ioctl:1,"is_a?":1,isatty:1,"iterator?":1,join:1,"key?":1,keys:1,"kind_of?":1,lambda:1,last:1,length:1,lineno:1,ljust:1,load:1,local_variables:1,loop:1,lstrip:1,"lstrip!":1,map:1,"map!":1,match:1,max:1,"member?":1,merge:1,"merge!":1,method:1,"method_defined?":1,method_missing:1,methods:1,min:1,module_eval:1,modulo:1,name:1,nesting:1,"new":1,next:1,"next!":1,"nil?":1,nitems:1,"nonzero?":1,object_id:1,oct:1,open:1,pack:1,partition:1,pid:1,pipe:1,pop:1,popen:1,pos:1,prec:1,prec_f:1,prec_i:1,print:1,printf:1,private_class_method:1,private_instance_methods:1,"private_method_defined?":1,private_methods:1,proc:1,protected_instance_methods:1,"protected_method_defined?":1,protected_methods:1,public_class_method:1,public_instance_methods:1,"public_method_defined?":1,public_methods:1,push:1,putc:1,puts:1,quo:1,raise:1,rand:1,rassoc:1,read:1,read_nonblock:1,readchar:1,readline:1,readlines:1,readpartial:1,rehash:1,reject:1,"reject!":1,remainder:1,reopen:1,replace:1,require:1,"respond_to?":1,reverse:1,"reverse!":1,reverse_each:1,rewind:1,rindex:1,rjust:1,round:1,rstrip:1,"rstrip!":1,scan:1,seek:1,select:1,send:1,set_trace_func:1,shift:1,singleton_method_added:1,singleton_methods:1,size:1,sleep:1,slice:1,"slice!":1,sort:1,"sort!":1,sort_by:1,split:1,sprintf:1,squeeze:1,"squeeze!":1,srand:1,stat:1,step:1,store:1,strip:1,"strip!":1,sub:1,"sub!":1,succ:1,"succ!":1,sum:1,superclass:1,swapcase:1,"swapcase!":1,sync:1,syscall:1,sysopen:1,sysread:1,sysseek:1,system:1,syswrite:1,taint:1,"tainted?":1,tell:1,test:1,"throw":1,times:1,to_a:1,to_ary:1,to_f:1,to_hash:1,to_i:1,to_int:1,to_io:1,to_proc:1,to_s:1,to_str:1,to_sym:1,tr:1,"tr!":1,tr_s:1,"tr_s!":1,trace_var:1,transpose:1,trap:1,truncate:1,"tty?":1,type:1,ungetc:1,uniq:1,"uniq!":1,unpack:1,unshift:1,untaint:1,untrace_var:1,upcase:1,"upcase!":1,update:1,upto:1,"value?":1,values:1,values_at:1,warn:1,write:1,write_nonblock:1,"zero?":1,zip:1}};var d={cN:"yardoctag",b:"@[A-Za-z]+"};var k=[{cN:"comment",b:"#",e:"$",c:[d]},{cN:"comment",b:"^\\=begin",e:"^\\=end",c:[d],r:10},{cN:"comment",b:"^__END__",e:"\\n$"}];var e={cN:"subst",b:"#\\{",e:"}",l:c,k:a};var g=[hljs.BE,e];var f=[{cN:"string",b:"'",e:"'",c:g,r:0},{cN:"string",b:'"',e:'"',c:g,r:0},{cN:"string",b:"%[qw]?\\(",e:"\\)",c:g,r:10},{cN:"string",b:"%[qw]?\\[",e:"\\]",c:g,r:10},{cN:"string",b:"%[qw]?{",e:"}",c:g,r:10},{cN:"string",b:"%[qw]?<",e:">",c:g,r:10},{cN:"string",b:"%[qw]?/",e:"/",c:g,r:10},{cN:"string",b:"%[qw]?%",e:"%",c:g,r:10},{cN:"string",b:"%[qw]?-",e:"-",c:g,r:10},{cN:"string",b:"%[qw]?\\|",e:"\\|",c:g,r:10}];var h={cN:"function",b:"\\bdef\\s+",e:" |$|;",l:c,k:a,c:[{cN:"title",b:i,l:c,k:a},{cN:"params",b:"\\(",e:"\\)",l:c,k:a}].concat(k)};var j={cN:"identifier",b:c,l:c,k:a,r:0};var b=k.concat(f.concat([{cN:"class",b:"\\b(class|module)\\b",e:"$|;",k:{"class":1,module:1},c:[{cN:"title",b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?",r:0},{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+hljs.IR+"::)?"+hljs.IR}]}].concat(k)},h,{cN:"constant",b:"(::)?([A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:":",c:f.concat([j]),r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"number",b:"\\?\\w"},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},j,{b:"("+hljs.RSR+")\\s*",c:k.concat([{cN:"regexp",b:"/",e:"/[a-z]*",i:"\\n",c:[hljs.BE]}]),r:0}]));e.c=b;h.c[1].c=b;return{dM:{l:c,k:a,c:b}}}();hljs.LANGUAGES.rust=function(){var c={cN:"title",b:hljs.UIR};var d={cN:"string",b:'"',e:'"',c:[hljs.BE],r:0};var b={cN:"number",b:"\\b(0[xb][A-Za-z0-9_]+|[0-9_]+(\\.[0-9_]+)?([uif](8|16|32|64)?)?)",r:0};var a={alt:1,any:1,as:1,assert:1,be:1,bind:1,block:1,bool:1,"break":1,"char":1,check:1,claim:1,"const":1,cont:1,dir:1,"do":1,"else":1,"enum":1,"export":1,f32:1,f64:1,fail:1,"false":1,"float":1,fn:10,"for":1,i16:1,i32:1,i64:1,i8:1,"if":1,iface:10,impl:10,"import":1,"in":1,"int":1,let:1,log:1,mod:1,mutable:1,"native":1,note:1,of:1,prove:1,pure:10,resource:1,ret:1,self:1,str:1,syntax:1,"true":1,type:1,u16:1,u32:1,u64:1,u8:1,uint:1,unchecked:1,unsafe:1,use:1,vec:1,"while":1};return{dM:{k:a,i:"]+"}]}]};return{cI:true,dM:{c:[{cN:"pi",b:"<\\?",e:"\\?>",r:10},{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:{style:1}},c:[a],starts:{cN:"css",e:"",rE:true,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:{script:1}},c:[a],starts:{cN:"javascript",e:"<\/script>",rE:true,sL:"javascript"}},{cN:"vbscript",b:"<%",e:"%>",sL:"vbscript"},{cN:"tag",b:"",c:[{cN:"title",b:"[^ />]+"},a]}]}}}();hljs.LANGUAGES.markdown={cI:true,dM:{c:[{cN:"header",b:"^#{1,3}",e:"$"},{cN:"header",b:"^.+?\\n[=-]{2,}$"},{b:"<",e:">",sL:"xml"},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",b:"[*_].+?[*_]"},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",b:"`.+?`"},{cN:"code",b:"^ ",e:"$",r:0},{cN:"horizontal_rule",b:"^-{3,}",e:"$"},{b:"\\[.+?\\]\\(.+?\\)",rB:true,c:[{cN:"link_label",b:"\\[.+\\]"},{cN:"link_url",b:"\\(",e:"\\)",eB:true,eE:true}]}]}};hljs.LANGUAGES.css=function(){var a={cN:"function",b:hljs.IR+"\\(",e:"\\)",c:[{eW:true,eE:true,c:[hljs.NM,hljs.ASM,hljs.QSM]}]};return{cI:true,dM:{i:"[=/|']",c:[hljs.CBLCLM,{cN:"id",b:"\\#[A-Za-z0-9_-]+"},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"pseudo",b:":(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\\"\\']+"},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:{"font-face":1,page:1}},{cN:"at_rule",b:"@",e:"[{;]",eE:true,k:{"import":1,page:1,media:1,charset:1},c:[a,hljs.ASM,hljs.QSM,hljs.NM]},{cN:"tag",b:hljs.IR,r:0},{cN:"rules",b:"{",e:"}",i:"[^\\s]",r:0,c:[hljs.CBLCLM,{cN:"rule",b:"[^\\s]",rB:true,e:";",eW:true,c:[{cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:true,i:"[^\\s]",starts:{cN:"value",eW:true,eE:true,c:[a,hljs.NM,hljs.QSM,hljs.ASM,hljs.CBLCLM,{cN:"hexcolor",b:"\\#[0-9A-F]+"},{cN:"important",b:"!important"}]}}]}]}]}}}();hljs.LANGUAGES.lisp=function(){var k="[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#]*";var m="(\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s)(\\+|\\-)?\\d+)?";var a={cN:"literal",b:"\\b(t{1}|nil)\\b"};var f=[{cN:"number",b:m},{cN:"number",b:"#b[0-1]+(/[0-1]+)?"},{cN:"number",b:"#o[0-7]+(/[0-7]+)?"},{cN:"number",b:"#x[0-9a-f]+(/[0-9a-f]+)?"},{cN:"number",b:"#c\\("+m+" +"+m,e:"\\)"}];var b={cN:"string",b:'"',e:'"',c:[hljs.BE],r:0};var l={cN:"comment",b:";",e:"$"};var c={cN:"variable",b:"\\*",e:"\\*"};var j={cN:"keyword",b:"[:&]"+k};var h={b:"\\(",e:"\\)",c:["self",a,b].concat(f)};var e={cN:"quoted",b:"['`]\\(",e:"\\)",c:f.concat([b,c,j,h])};var d={cN:"quoted",b:"\\(quote ",e:"\\)",k:{title:{quote:1}},c:f.concat([b,c,j,h])};var i={cN:"list",b:"\\(",e:"\\)"};var g={cN:"body",eW:true,eE:true};i.c=[{cN:"title",b:k},g];g.c=[e,d,i,a].concat(f).concat([b,l,c,j]);return{cI:true,dM:{i:"[^\\s]",c:f.concat([a,b,l,e,d,i])}}}();hljs.LANGUAGES.profile={dM:{c:[hljs.CNM,{cN:"builtin",b:"{",e:"}$",eB:true,eE:true,c:[hljs.ASM,hljs.QSM],r:0},{cN:"filename",b:"[a-zA-Z_][\\da-zA-Z_]+\\.[\\da-zA-Z_]{1,3}",e:":",eE:true},{cN:"header",b:"(ncalls|tottime|cumtime)",e:"$",k:{ncalls:1,tottime:10,cumtime:10,filename:1},r:10},{cN:"summary",b:"function calls",e:"$",c:[hljs.CNM],r:10},hljs.ASM,hljs.QSM,{cN:"function",b:"\\(",e:"\\)$",c:[{cN:"title",b:hljs.UIR,r:0}],r:0}]}};hljs.LANGUAGES.java={dM:{k:{"false":1,"synchronized":1,"int":1,"abstract":1,"float":1,"private":1,"char":1,"interface":1,"boolean":1,"static":1,"null":1,"if":1,"const":1,"for":1,"true":1,"while":1,"long":1,"throw":1,strictfp:1,"finally":1,"protected":1,"extends":1,"import":1,"native":1,"final":1,"implements":1,"return":1,"void":1,"enum":1,"else":1,"break":1,"transient":1,"new":1,"catch":1,"instanceof":1,"byte":1,"super":1,"class":1,"volatile":1,"case":1,assert:1,"short":1,"package":1,"default":1,"double":1,"public":1,"try":1,"this":1,"switch":1,"continue":1,"throws":1},c:[{cN:"javadoc",b:"/\\*\\*",e:"\\*/",c:[{cN:"javadoctag",b:"@[A-Za-z]+"}],r:10},hljs.CLCM,hljs.CBLCLM,hljs.ASM,hljs.QSM,{cN:"class",b:"(class |interface )",e:"{",k:{"class":1,"interface":1},i:":",c:[{b:"(implements|extends)",k:{"extends":1,"implements":1},r:10},{cN:"title",b:hljs.UIR}]},hljs.CNM,{cN:"annotation",b:"@[A-Za-z]+"}]}};hljs.LANGUAGES.php={cI:true,dM:{k:{and:1,include_once:1,list:1,"abstract":1,global:1,"private":1,echo:1,"interface":1,as:1,"static":1,endswitch:1,array:1,"null":1,"if":1,endwhile:1,or:1,"const":1,"for":1,endforeach:1,self:1,"var":1,"while":1,isset:1,"public":1,"protected":1,exit:1,foreach:1,"throw":1,elseif:1,"extends":1,include:1,__FILE__:1,empty:1,require_once:1,"function":1,"do":1,xor:1,"return":1,"implements":1,parent:1,clone:1,use:1,__CLASS__:1,__LINE__:1,"else":1,"break":1,print:1,"eval":1,"new":1,"catch":1,__METHOD__:1,"class":1,"case":1,exception:1,php_user_filter:1,"default":1,die:1,require:1,__FUNCTION__:1,enddeclare:1,"final":1,"try":1,"this":1,"switch":1,"continue":1,endfor:1,endif:1,declare:1,unset:1,"true":1,"false":1,namespace:1,trait:1,"goto":1,"instanceof":1,__DIR__:1,__NAMESPACE__:1,__halt_compiler:1},c:[hljs.CLCM,hljs.HCM,{cN:"comment",b:"/\\*",e:"\\*/",c:[{cN:"phpdoc",b:"\\s@[A-Za-z]+"}]},{cN:"comment",eB:true,b:"__halt_compiler[^;]+;",e:"[\\n\\r]$"},hljs.CNM,hljs.BINARY_NUMBER_MODE,hljs.inherit(hljs.ASM,{i:null}),hljs.inherit(hljs.QSM,{i:null}),{cN:"string",b:'b"',e:'"',c:[hljs.BE]},{cN:"string",b:"b'",e:"'",c:[hljs.BE]},{cN:"string",b:"<<<['\"]?\\w+['\"]?$",e:"^\\w+;",c:[hljs.BE]},{cN:"variable",b:"\\$+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*"},{cN:"preprocessor",b:"<\\?php",r:10},{cN:"preprocessor",b:"\\?>"}]}};hljs.LANGUAGES.haskell=function(){var a={cN:"label",b:"\\b[A-Z][\\w']*",r:0};var b={cN:"container",b:"\\(",e:"\\)",c:[{cN:"label",b:"\\b[A-Z][\\w\\(\\)\\.']*"},{cN:"title",b:"[_a-z][\\w']*"}]};return{dM:{k:{keyword:{let:1,"in":1,"if":1,then:1,"else":1,"case":1,of:1,where:1,"do":1,module:1,"import":1,hiding:1,qualified:1,type:1,data:1,newtype:1,deriving:1,"class":1,instance:1,"null":1,not:1,as:1}},c:[{cN:"comment",b:"--",e:"$"},{cN:"comment",b:"{-",e:"-}"},{cN:"string",b:"\\s+'",e:"'",c:[hljs.BE],r:0},hljs.QSM,{cN:"import",b:"\\bimport",e:"$",k:{"import":1,qualified:1,as:1,hiding:1},c:[b]},{cN:"module",b:"\\bmodule",e:"where",k:{module:1,where:1},c:[b]},{cN:"class",b:"\\b(class|instance|data|(new)?type)",e:"(where|$)",k:{"class":1,where:1,instance:1,data:1,type:1,newtype:1,deriving:1},c:[a]},hljs.CNM,{cN:"shebang",b:"#!\\/usr\\/bin\\/env runhaskell",e:"$"},a,{cN:"title",b:"^[_a-z][\\w']*"}]}}}();hljs.LANGUAGES["1c"]=function(){var b="[a-zA-Zа-яА-Я][a-zA-Z0-9_а-яА-Я]*";var e={"возврат":1,"дата":1,"для":1,"если":1,"и":1,"или":1,"иначе":1,"иначеесли":1,"исключение":1,"конецесли":1,"конецпопытки":1,"конецпроцедуры":1,"конецфункции":1,"конеццикла":1,"константа":1,"не":1,"перейти":1,"перем":1,"перечисление":1,"по":1,"пока":1,"попытка":1,"прервать":1,"продолжить":1,"процедура":1,"строка":1,"тогда":1,"фс":1,"функция":1,"цикл":1,"число":1,"экспорт":1};var d={ansitooem:1,oemtoansi:1,"ввестивидсубконто":1,"ввестидату":1,"ввестизначение":1,"ввестиперечисление":1,"ввестипериод":1,"ввестиплансчетов":1,"ввестистроку":1,"ввестичисло":1,"вопрос":1,"восстановитьзначение":1,"врег":1,"выбранныйплансчетов":1,"вызватьисключение":1,"датагод":1,"датамесяц":1,"датачисло":1,"добавитьмесяц":1,"завершитьработусистемы":1,"заголовоксистемы":1,"записьжурналарегистрации":1,"запуститьприложение":1,"зафиксироватьтранзакцию":1,"значениевстроку":1,"значениевстрокувнутр":1,"значениевфайл":1,"значениеизстроки":1,"значениеизстрокивнутр":1,"значениеизфайла":1,"имякомпьютера":1,"имяпользователя":1,"каталогвременныхфайлов":1,"каталогиб":1,"каталогпользователя":1,"каталогпрограммы":1,"кодсимв":1,"командасистемы":1,"конгода":1,"конецпериодаби":1,"конецрассчитанногопериодаби":1,"конецстандартногоинтервала":1,"конквартала":1,"конмесяца":1,"коннедели":1,"лев":1,"лог":1,"лог10":1,"макс":1,"максимальноеколичествосубконто":1,"мин":1,"монопольныйрежим":1,"названиеинтерфейса":1,"названиенабораправ":1,"назначитьвид":1,"назначитьсчет":1,"найти":1,"найтипомеченныенаудаление":1,"найтиссылки":1,"началопериодаби":1,"началостандартногоинтервала":1,"начатьтранзакцию":1,"начгода":1,"начквартала":1,"начмесяца":1,"начнедели":1,"номерднягода":1,"номерднянедели":1,"номернеделигода":1,"нрег":1,"обработкаожидания":1,"окр":1,"описаниеошибки":1,"основнойжурналрасчетов":1,"основнойплансчетов":1,"основнойязык":1,"открытьформу":1,"открытьформумодально":1,"отменитьтранзакцию":1,"очиститьокносообщений":1,"периодстр":1,"полноеимяпользователя":1,"получитьвремята":1,"получитьдатута":1,"получитьдокументта":1,"получитьзначенияотбора":1,"получитьпозициюта":1,"получитьпустоезначение":1,"получитьта":1,"прав":1,"праводоступа":1,"предупреждение":1,"префиксавтонумерации":1,"пустаястрока":1,"пустоезначение":1,"рабочаядаттьпустоезначение":1,"рабочаядата":1,"разделительстраниц":1,"разделительстрок":1,"разм":1,"разобратьпозициюдокумента":1,"рассчитатьрегистрына":1,"рассчитатьрегистрыпо":1,"сигнал":1,"симв":1,"символтабуляции":1,"создатьобъект":1,"сокрл":1,"сокрлп":1,"сокрп":1," сообщить":1,"состояние":1,"сохранитьзначение":1,"сред":1,"статусвозврата":1,"стрдлина":1,"стрзаменить":1,"стрколичествострок":1,"стрполучитьстроку":1," стрчисловхождений":1,"сформироватьпозициюдокумента":1,"счетпокоду":1,"текущаядата":1,"текущеевремя":1,"типзначения":1,"типзначениястр":1,"удалитьобъекты":1,"установитьтана":1,"установитьтапо":1,"фиксшаблон":1,"формат":1,"цел":1,"шаблон":1};var a={cN:"dquote",b:'""'};var c={cN:"string",b:'"',e:'"|$',c:[a],r:0};var f={cN:"string",b:"\\|",e:'"|$',c:[a]};return{cI:true,dM:{l:b,k:{keyword:e,built_in:d},c:[hljs.CLCM,hljs.NM,c,f,{cN:"function",b:"(процедура|функция)",e:"$",l:b,k:{"процедура":1,"экспорт":1,"функция":1},c:[{cN:"title",b:b},{cN:"tail",eW:true,c:[{cN:"params",b:"\\(",e:"\\)",l:b,k:{"знач":1},c:[c,f]},{cN:"export",b:"экспорт",eW:true,l:b,k:{"экспорт":1},c:[hljs.CLCM]}]},hljs.CLCM]},{cN:"preprocessor",b:"#",e:"$"},{cN:"date",b:"'\\d{2}\\.\\d{2}\\.(\\d{2}|\\d{4})'"}]}}}();hljs.LANGUAGES.python=function(){var a=[{cN:"string",b:"(u|b)?r?'''",e:"'''",r:10},{cN:"string",b:'(u|b)?r?"""',e:'"""',r:10},{cN:"string",b:"(u|r|ur)'",e:"'",c:[hljs.BE],r:10},{cN:"string",b:'(u|r|ur)"',e:'"',c:[hljs.BE],r:10},{cN:"string",b:"(b|br)'",e:"'",c:[hljs.BE]},{cN:"string",b:'(b|br)"',e:'"',c:[hljs.BE]}].concat([hljs.ASM,hljs.QSM]);var b={cN:"title",b:hljs.UIR};var c={cN:"params",b:"\\(",e:"\\)",c:a.concat([hljs.CNM])};return{dM:{k:{keyword:{and:1,elif:1,is:1,global:1,as:1,"in":1,"if":1,from:1,raise:1,"for":1,except:1,"finally":1,print:1,"import":1,pass:1,"return":1,exec:1,"else":1,"break":1,not:1,"with":1,"class":1,assert:1,yield:1,"try":1,"while":1,"continue":1,del:1,or:1,def:1,lambda:1,nonlocal:10},built_in:{None:1,True:1,False:1,Ellipsis:1,NotImplemented:1}},i:"(|\\?)",c:a.concat([hljs.HCM,{cN:"function",b:"\\bdef ",e:":",i:"$",k:{def:1},c:[b,c],r:10},{cN:"class",b:"\\bclass ",e:":",i:"[${]",k:{"class":1},c:[b,c],r:10},hljs.CNM,{cN:"decorator",b:"@",e:"$"}])}}}();hljs.LANGUAGES.smalltalk=function(){var b="[a-z][a-zA-Z0-9_]*";var c={cN:"char",b:"\\$.{1}"};var a={cN:"symbol",b:"#"+hljs.UIR};return{dM:{k:{self:1,"super":1,nil:1,"true":1,"false":1,thisContext:1},c:[{cN:"comment",b:'"',e:'"',r:0},hljs.ASM,{cN:"class",b:"\\b[A-Z][A-Za-z0-9_]*",r:0},{cN:"method",b:b+":"},hljs.CNM,a,c,{cN:"localvars",b:"\\|\\s*(("+b+")\\s*)+\\|"},{cN:"array",b:"\\#\\(",e:"\\)",c:[hljs.ASM,c,hljs.CNM,a]}]}}}();hljs.LANGUAGES.tex=function(){var c={cN:"command",b:"\\\\[a-zA-Zа-яА-я]+[\\*]?",r:10};var b={cN:"command",b:"\\\\[^a-zA-Zа-яА-я0-9]",r:0};var a={cN:"special",b:"[{}\\[\\]\\&#~]",r:0};return{dM:{c:[{b:"\\\\[a-zA-Zа-яА-я]+[\\*]? *= *-?\\d*\\.?\\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?",rB:true,c:[c,b,{cN:"number",b:" *=",e:"-?\\d*\\.?\\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?",eB:true}],r:10},c,b,a,{cN:"formula",b:"\\$\\$",e:"\\$\\$",c:[c,b,a],r:0},{cN:"formula",b:"\\$",e:"\\$",c:[c,b,a],r:0},{cN:"comment",b:"%",e:"$",r:0}]}}}();hljs.LANGUAGES.actionscript=function(){var c="[a-zA-Z_$][a-zA-Z0-9_$]*";var a="([*]|[a-zA-Z_$][a-zA-Z0-9_$]*)";var d={cN:"rest_arg",b:"[.]{3}",e:c,r:10};var b={cN:"title",b:c};return{dM:{k:{keyword:{as:1,"break":1,"case":1,"catch":1,"class":1,"const":1,"continue":1,"default":1,"delete":1,"do":1,dynamic:5,each:1,"else":1,"extends":1,"final":1,"finally":1,"for":1,"function":1,get:1,"if":1,"implements":1,"import":1,"in":1,include:1,"instanceof":1,"interface":1,internal:1,is:1,namespace:1,"native":1,"new":1,override:1,"package":1,"private":1,"protected":1,"public":1,"return":1,set:1,"static":1,"super":5,"switch":1,"this":1,"throw":1,"try":1,"typeof":1,use:1,"var":1,"void":1,"while":1,"with":1},literal:{"true":1,"false":1,"null":1,"undefined":1},reserved:{"abstract":0,"boolean":0,"byte":0,cast:0,"char":0,"debugger":0,"double":0,"enum":0,"export":0,"float":0,"goto":0,intrinsic:0,"long":0,prototype:0,"short":0,"synchronized":0,"throws":0,to:0,"transient":0,type:0,virtual:0,"volatile":0}},c:[hljs.ASM,hljs.QSM,hljs.CLCM,hljs.CBLCLM,hljs.CNM,{cN:"package",b:"package ?",e:"{",k:{"package":1},c:[b]},{cN:"class",b:"(class|interface) ",e:"{",k:{"class":1,"interface":1},c:[{b:"(implements|extends)",k:{"extends":1,"implements":1},r:5},b]},{cN:"preprocessor",b:"(import|include)\\b",e:";",k:{"import":1,include:1}},{cN:"function",b:"function ",e:"[{;]",k:{"function":1},c:[b,{cN:"params",b:"\\(",e:"\\)",c:[hljs.ASM,hljs.QSM,hljs.CLCM,hljs.CBLCLM,d]},{cN:"type",b:":",e:a,r:10}]}]}}}();hljs.LANGUAGES.sql={cI:true,dM:{i:"[^\\s]",c:[{cN:"operator",b:"(begin|start|commit|rollback|savepoint|lock|alter|create|drop|rename|call|delete|do|handler|insert|load|replace|select|truncate|update|set|show|pragma|grant)\\b",e:";|$",k:{keyword:{all:1,partial:1,global:1,month:1,current_timestamp:1,using:1,go:1,revoke:1,smallint:1,indicator:1,"end-exec":1,disconnect:1,zone:1,"with":1,character:1,assertion:1,to:1,add:1,current_user:1,usage:1,input:1,local:1,alter:1,match:1,collate:1,real:1,then:1,rollback:1,get:1,read:1,timestamp:1,session_user:1,not:1,integer:1,bit:1,unique:1,day:1,minute:1,desc:1,insert:1,execute:1,like:1,ilike:2,level:1,decimal:1,drop:1,"continue":1,isolation:1,found:1,where:1,constraints:1,domain:1,right:1,national:1,some:1,module:1,transaction:1,relative:1,second:1,connect:1,escape:1,close:1,system_user:1,"for":1,deferred:1,section:1,cast:1,current:1,sqlstate:1,allocate:1,intersect:1,deallocate:1,numeric:1,"public":1,preserve:1,full:1,"goto":1,initially:1,asc:1,no:1,key:1,output:1,collation:1,group:1,by:1,union:1,session:1,both:1,last:1,language:1,constraint:1,column:1,of:1,space:1,foreign:1,deferrable:1,prior:1,connection:1,unknown:1,action:1,commit:1,view:1,or:1,first:1,into:1,"float":1,year:1,primary:1,cascaded:1,except:1,restrict:1,set:1,references:1,names:1,table:1,outer:1,open:1,select:1,size:1,are:1,rows:1,from:1,prepare:1,distinct:1,leading:1,create:1,only:1,next:1,inner:1,authorization:1,schema:1,corresponding:1,option:1,declare:1,precision:1,immediate:1,"else":1,timezone_minute:1,external:1,varying:1,translation:1,"true":1,"case":1,exception:1,join:1,hour:1,"default":1,"double":1,scroll:1,value:1,cursor:1,descriptor:1,values:1,dec:1,fetch:1,procedure:1,"delete":1,and:1,"false":1,"int":1,is:1,describe:1,"char":1,as:1,at:1,"in":1,varchar:1,"null":1,trailing:1,any:1,absolute:1,current_time:1,end:1,grant:1,privileges:1,when:1,cross:1,check:1,write:1,current_date:1,pad:1,begin:1,temporary:1,exec:1,time:1,update:1,catalog:1,user:1,sql:1,date:1,on:1,identity:1,timezone_hour:1,natural:1,whenever:1,interval:1,work:1,order:1,cascade:1,diagnostics:1,nchar:1,having:1,left:1,call:1,"do":1,handler:1,load:1,replace:1,truncate:1,start:1,lock:1,show:1,pragma:1},aggregate:{count:1,sum:1,min:1,max:1,avg:1}},c:[{cN:"string",b:"'",e:"'",c:[hljs.BE,{b:"''"}],r:0},{cN:"string",b:'"',e:'"',c:[hljs.BE,{b:'""'}],r:0},{cN:"string",b:"`",e:"`",c:[hljs.BE]},hljs.CNM,{b:"\\n"}]},hljs.CBLCLM,{cN:"comment",b:"--",e:"$"}]}};hljs.LANGUAGES.vala={dM:{k:{keyword:{"char":1,uchar:1,unichar:1,"int":1,uint:1,"long":1,ulong:1,"short":1,ushort:1,int8:1,int16:1,int32:1,int64:1,uint8:1,uint16:1,uint32:1,uint64:1,"float":1,"double":1,bool:1,struct:1,"enum":1,string:1,"void":1,weak:5,unowned:5,owned:5,async:5,signal:5,"static":1,"abstract":1,"interface":1,override:1,"while":1,"do":1,"for":1,foreach:1,"else":1,"switch":1,"case":1,"break":1,"default":1,"return":1,"try":1,"catch":1,"public":1,"private":1,"protected":1,internal:1,using:1,"new":1,"this":1,get:1,set:1,"const":1,stdout:1,stdin:1,stderr:1,"var":1,DBus:2,GLib:2,CCode:10,Gee:10,Object:1},literal:{"false":1,"true":1,"null":1}},c:[{cN:"class",b:"(class |interface |delegate |namespace )",e:"{",k:{"class":1,"interface":1},c:[{b:"(implements|extends)",k:{"extends":1,"implements":1}},{cN:"title",b:hljs.UIR}]},hljs.CLCM,hljs.CBLCLM,{cN:"string",b:'"""',e:'"""',r:5},hljs.ASM,hljs.QSM,hljs.CNM,{cN:"preprocessor",b:"^#",e:"$",r:2},{cN:"constant",b:" [A-Z_]+ ",r:0}]}};hljs.LANGUAGES.ini={cI:true,dM:{i:"[^\\s]",c:[{cN:"comment",b:";",e:"$"},{cN:"title",b:"^\\[",e:"\\]"},{cN:"setting",b:"^[a-z0-9_\\[\\]]+[ \\t]*=[ \\t]*",e:"$",c:[{cN:"value",eW:true,k:{on:1,off:1,"true":1,"false":1,yes:1,no:1},c:[hljs.QSM,hljs.NM]}]}]}};hljs.LANGUAGES.axapta={dM:{k:{"false":1,"int":1,"abstract":1,"private":1,"char":1,"interface":1,"boolean":1,"static":1,"null":1,"if":1,"for":1,"true":1,"while":1,"long":1,"throw":1,"finally":1,"protected":1,"extends":1,"final":1,"implements":1,"return":1,"void":1,"enum":1,"else":1,"break":1,"new":1,"catch":1,"byte":1,"super":1,"class":1,"case":1,"short":1,"default":1,"double":1,"public":1,"try":1,"this":1,"switch":1,"continue":1,reverse:1,firstfast:1,firstonly:1,forupdate:1,nofetch:1,sum:1,avg:1,minof:1,maxof:1,count:1,order:1,group:1,by:1,asc:1,desc:1,index:1,hint:1,like:1,dispaly:1,edit:1,client:1,server:1,ttsbegin:1,ttscommit:1,str:1,real:1,date:1,container:1,anytype:1,common:1,div:1,mod:1},c:[hljs.CLCM,hljs.CBLCLM,hljs.ASM,hljs.QSM,hljs.CNM,{cN:"preprocessor",b:"#",e:"$"},{cN:"class",b:"(class |interface )",e:"{",i:":",k:{"class":1,"interface":1},c:[{cN:"inheritance",b:"(implements|extends)",k:{"extends":1,"implements":1},r:10},{cN:"title",b:hljs.UIR}]}]}};hljs.LANGUAGES.perl=function(){var c={getpwent:1,getservent:1,quotemeta:1,msgrcv:1,scalar:1,kill:1,dbmclose:1,undef:1,lc:1,ma:1,syswrite:1,tr:1,send:1,umask:1,sysopen:1,shmwrite:1,vec:1,qx:1,utime:1,local:1,oct:1,semctl:1,localtime:1,readpipe:1,"do":1,"return":1,format:1,read:1,sprintf:1,dbmopen:1,pop:1,getpgrp:1,not:1,getpwnam:1,rewinddir:1,qq:1,fileno:1,qw:1,endprotoent:1,wait:1,sethostent:1,bless:1,s:1,opendir:1,"continue":1,each:1,sleep:1,endgrent:1,shutdown:1,dump:1,chomp:1,connect:1,getsockname:1,die:1,socketpair:1,close:1,flock:1,exists:1,index:1,shmget:1,sub:1,"for":1,endpwent:1,redo:1,lstat:1,msgctl:1,setpgrp:1,abs:1,exit:1,select:1,print:1,ref:1,gethostbyaddr:1,unshift:1,fcntl:1,syscall:1,"goto":1,getnetbyaddr:1,join:1,gmtime:1,symlink:1,semget:1,splice:1,x:1,getpeername:1,recv:1,log:1,setsockopt:1,cos:1,last:1,reverse:1,gethostbyname:1,getgrnam:1,study:1,formline:1,endhostent:1,times:1,chop:1,length:1,gethostent:1,getnetent:1,pack:1,getprotoent:1,getservbyname:1,rand:1,mkdir:1,pos:1,chmod:1,y:1,substr:1,endnetent:1,printf:1,next:1,open:1,msgsnd:1,readdir:1,use:1,unlink:1,getsockopt:1,getpriority:1,rindex:1,wantarray:1,hex:1,system:1,getservbyport:1,endservent:1,"int":1,chr:1,untie:1,rmdir:1,prototype:1,tell:1,listen:1,fork:1,shmread:1,ucfirst:1,setprotoent:1,"else":1,sysseek:1,link:1,getgrgid:1,shmctl:1,waitpid:1,unpack:1,getnetbyname:1,reset:1,chdir:1,grep:1,split:1,require:1,caller:1,lcfirst:1,until:1,warn:1,"while":1,values:1,shift:1,telldir:1,getpwuid:1,my:1,getprotobynumber:1,"delete":1,and:1,sort:1,uc:1,defined:1,srand:1,accept:1,"package":1,seekdir:1,getprotobyname:1,semop:1,our:1,rename:1,seek:1,"if":1,q:1,chroot:1,sysread:1,setpwent:1,no:1,crypt:1,getc:1,chown:1,sqrt:1,write:1,setnetent:1,setpriority:1,foreach:1,tie:1,sin:1,msgget:1,map:1,stat:1,getlogin:1,unless:1,elsif:1,truncate:1,exec:1,keys:1,glob:1,tied:1,closedir:1,ioctl:1,socket:1,readlink:1,"eval":1,xor:1,readline:1,binmode:1,setservent:1,eof:1,ord:1,bind:1,alarm:1,pipe:1,atan2:1,getgrent:1,exp:1,time:1,push:1,setgrent:1,gt:1,lt:1,or:1,ne:1,m:1};var e={cN:"subst",b:"[$@]\\{",e:"\\}",k:c,r:10};var b={cN:"variable",b:"\\$\\d"};var a={cN:"variable",b:"[\\$\\%\\@\\*](\\^\\w\\b|#\\w+(\\:\\:\\w+)*|[^\\s\\w{]|{\\w+}|\\w+(\\:\\:\\w*)*)"};var h=[hljs.BE,e,b,a];var g={b:"->",c:[{b:hljs.IR},{b:"{",e:"}"}]};var d={cN:"comment",b:"^(__END__|__DATA__)",e:"\\n$",r:5};var f=[b,a,hljs.HCM,d,g,{cN:"string",b:"q[qwxr]?\\s*\\(",e:"\\)",c:h,r:5},{cN:"string",b:"q[qwxr]?\\s*\\[",e:"\\]",c:h,r:5},{cN:"string",b:"q[qwxr]?\\s*\\{",e:"\\}",c:h,r:5},{cN:"string",b:"q[qwxr]?\\s*\\|",e:"\\|",c:h,r:5},{cN:"string",b:"q[qwxr]?\\s*\\<",e:"\\>",c:h,r:5},{cN:"string",b:"qw\\s+q",e:"q",c:h,r:5},{cN:"string",b:"'",e:"'",c:[hljs.BE],r:0},{cN:"string",b:'"',e:'"',c:h,r:0},{cN:"string",b:"`",e:"`",c:[hljs.BE]},{cN:"string",b:"{\\w+}",r:0},{cN:"string",b:"-?\\w+\\s*\\=\\>",r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"("+hljs.RSR+"|split|return|print|reverse|grep)\\s*",k:{split:1,"return":1,print:1,reverse:1,grep:1},r:0,c:[hljs.HCM,d,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[hljs.BE],r:0}]},{cN:"sub",b:"\\bsub\\b",e:"(\\s*\\(.*?\\))?[;{]",k:{sub:1},r:5},{cN:"operator",b:"-\\w\\b",r:0},{cN:"pod",b:"\\=\\w",e:"\\=cut"}];e.c=f;g.c[1].c=f;return{dM:{k:c,c:f}}}();hljs.LANGUAGES.scala=function(){var a={cN:"annotation",b:"@[A-Za-z]+"};var b={cN:"string",b:'u?r?"""',e:'"""',r:10};return{dM:{k:{type:1,yield:1,lazy:1,override:1,def:1,"with":1,val:1,"var":1,"false":1,"true":1,sealed:1,"abstract":1,"private":1,trait:1,object:1,"null":1,"if":1,"for":1,"while":1,"throw":1,"finally":1,"protected":1,"extends":1,"import":1,"final":1,"return":1,"else":1,"break":1,"new":1,"catch":1,"super":1,"class":1,"case":1,"package":1,"default":1,"try":1,"this":1,match:1,"continue":1,"throws":1},c:[{cN:"javadoc",b:"/\\*\\*",e:"\\*/",c:[{cN:"javadoctag",b:"@[A-Za-z]+"}],r:10},hljs.CLCM,hljs.CBLCLM,hljs.ASM,hljs.QSM,b,{cN:"class",b:"((case )?class |object |trait )",e:"({|$)",i:":",k:{"case":1,"class":1,trait:1,object:1},c:[{b:"(extends|with)",k:{"extends":1,"with":1},r:10},{cN:"title",b:hljs.UIR},{cN:"params",b:"\\(",e:"\\)",c:[hljs.ASM,hljs.QSM,b,a]}]},hljs.CNM,a]}}}();hljs.LANGUAGES.cmake={cI:true,dM:{k:{add_custom_command:2,add_custom_target:2,add_definitions:2,add_dependencies:2,add_executable:2,add_library:2,add_subdirectory:2,add_test:2,aux_source_directory:2,"break":1,build_command:2,cmake_minimum_required:3,cmake_policy:3,configure_file:1,create_test_sourcelist:1,define_property:1,"else":1,elseif:1,enable_language:2,enable_testing:2,endforeach:1,endfunction:1,endif:1,endmacro:1,endwhile:1,execute_process:2,"export":1,find_file:1,find_library:2,find_package:2,find_path:1,find_program:1,fltk_wrap_ui:2,foreach:1,"function":1,get_cmake_property:3,get_directory_property:1,get_filename_component:1,get_property:1,get_source_file_property:1,get_target_property:1,get_test_property:1,"if":1,include:1,include_directories:2,include_external_msproject:1,include_regular_expression:2,install:1,link_directories:1,load_cache:1,load_command:1,macro:1,mark_as_advanced:1,message:1,option:1,output_required_files:1,project:1,qt_wrap_cpp:2,qt_wrap_ui:2,remove_definitions:2,"return":1,separate_arguments:1,set:1,set_directory_properties:1,set_property:1,set_source_files_properties:1,set_target_properties:1,set_tests_properties:1,site_name:1,source_group:1,string:1,target_link_libraries:2,try_compile:2,try_run:2,unset:1,variable_watch:2,"while":1,build_name:1,exec_program:1,export_library_dependencies:1,install_files:1,install_programs:1,install_targets:1,link_libraries:1,make_directory:1,remove:1,subdir_depends:1,subdirs:1,use_mangled_mesa:1,utility_source:1,variable_requires:1,write_file:1},c:[{cN:"envvar",b:"\\${",e:"}"},hljs.HCM,hljs.QSM,hljs.NM]}};hljs.LANGUAGES.objectivec=function(){var a={keyword:{"false":1,"int":1,"float":1,"while":1,"private":1,"char":1,"catch":1,"export":1,sizeof:2,typedef:2,"const":1,struct:1,"for":1,union:1,unsigned:1,"long":1,"volatile":2,"static":1,"protected":1,bool:1,mutable:1,"if":1,"public":1,"do":1,"return":1,"goto":1,"void":2,"enum":1,"else":1,"break":1,extern:1,"true":1,"class":1,asm:1,"case":1,"short":1,"default":1,"double":1,"throw":1,register:1,explicit:1,signed:1,typename:1,"try":1,"this":1,"switch":1,"continue":1,wchar_t:1,inline:1,readonly:1,assign:1,property:1,protocol:10,self:1,"synchronized":1,end:1,synthesize:50,id:1,optional:1,required:1,implementation:10,nonatomic:1,"interface":1,"super":1,unichar:1,"finally":2,dynamic:2,nil:1},built_in:{YES:5,NO:5,NULL:1,IBOutlet:50,IBAction:50,NSString:50,NSDictionary:50,CGRect:50,CGPoint:50,NSRange:50,release:1,retain:1,autorelease:50,UIButton:50,UILabel:50,UITextView:50,UIWebView:50,MKMapView:50,UISegmentedControl:50,NSObject:50,UITableViewDelegate:50,UITableViewDataSource:50,NSThread:50,UIActivityIndicator:50,UITabbar:50,UIToolBar:50,UIBarButtonItem:50,UIImageView:50,NSAutoreleasePool:50,UITableView:50,BOOL:1,NSInteger:20,CGFloat:20,NSException:50,NSLog:50,NSMutableString:50,NSMutableArray:50,NSMutableDictionary:50,NSURL:50}};return{dM:{k:a,i:""}]},{cN:"preprocessor",b:"#",e:"$"},{cN:"class",b:"interface|class|protocol|implementation",e:"({|$)",k:{"interface":1,"class":1,protocol:5,implementation:5},c:[{cN:"id",b:hljs.UIR}]}]}}}();hljs.LANGUAGES.avrasm={cI:true,dM:{k:{keyword:{adc:1,add:1,adiw:1,and:1,andi:1,asr:1,bclr:1,bld:1,brbc:1,brbs:1,brcc:1,brcs:1,"break":1,breq:1,brge:1,brhc:1,brhs:1,brid:1,brie:1,brlo:1,brlt:1,brmi:1,brne:1,brpl:1,brsh:1,brtc:1,brts:1,brvc:1,brvs:1,bset:1,bst:1,call:1,cbi:1,cbr:1,clc:1,clh:1,cli:1,cln:1,clr:1,cls:1,clt:1,clv:1,clz:1,com:1,cp:1,cpc:1,cpi:1,cpse:1,dec:1,eicall:1,eijmp:1,elpm:1,eor:1,fmul:1,fmuls:1,fmulsu:1,icall:1,ijmp:1,"in":1,inc:1,jmp:1,ld:1,ldd:1,ldi:1,lds:1,lpm:1,lsl:1,lsr:1,mov:1,movw:1,mul:1,muls:1,mulsu:1,neg:1,nop:1,or:1,ori:1,out:1,pop:1,push:1,rcall:1,ret:1,reti:1,rjmp:1,rol:1,ror:1,sbc:1,sbr:1,sbrc:1,sbrs:1,sec:1,seh:1,sbi:1,sbci:1,sbic:1,sbis:1,sbiw:1,sei:1,sen:1,ser:1,ses:1,set:1,sev:1,sez:1,sleep:1,spm:1,st:1,std:1,sts:1,sub:1,subi:1,swap:1,tst:1,wdr:1},built_in:{r0:1,r1:1,r2:1,r3:1,r4:1,r5:1,r6:1,r7:1,r8:1,r9:1,r10:1,r11:1,r12:1,r13:1,r14:1,r15:1,r16:1,r17:1,r18:1,r19:1,r20:1,r21:1,r22:1,r23:1,r24:1,r25:1,r26:1,r27:1,r28:1,r29:1,r30:1,r31:1,x:1,xh:1,xl:1,y:1,yh:1,yl:1,z:1,zh:1,zl:1,ucsr1c:1,udr1:1,ucsr1a:1,ucsr1b:1,ubrr1l:1,ubrr1h:1,ucsr0c:1,ubrr0h:1,tccr3c:1,tccr3a:1,tccr3b:1,tcnt3h:1,tcnt3l:1,ocr3ah:1,ocr3al:1,ocr3bh:1,ocr3bl:1,ocr3ch:1,ocr3cl:1,icr3h:1,icr3l:1,etimsk:1,etifr:1,tccr1c:1,ocr1ch:1,ocr1cl:1,twcr:1,twdr:1,twar:1,twsr:1,twbr:1,osccal:1,xmcra:1,xmcrb:1,eicra:1,spmcsr:1,spmcr:1,portg:1,ddrg:1,ping:1,portf:1,ddrf:1,sreg:1,sph:1,spl:1,xdiv:1,rampz:1,eicrb:1,eimsk:1,gimsk:1,gicr:1,eifr:1,gifr:1,timsk:1,tifr:1,mcucr:1,mcucsr:1,tccr0:1,tcnt0:1,ocr0:1,assr:1,tccr1a:1,tccr1b:1,tcnt1h:1,tcnt1l:1,ocr1ah:1,ocr1al:1,ocr1bh:1,ocr1bl:1,icr1h:1,icr1l:1,tccr2:1,tcnt2:1,ocr2:1,ocdr:1,wdtcr:1,sfior:1,eearh:1,eearl:1,eedr:1,eecr:1,porta:1,ddra:1,pina:1,portb:1,ddrb:1,pinb:1,portc:1,ddrc:1,pinc:1,portd:1,ddrd:1,pind:1,spdr:1,spsr:1,spcr:1,udr0:1,ucsr0a:1,ucsr0b:1,ubrr0l:1,acsr:1,admux:1,adcsr:1,adch:1,adcl:1,porte:1,ddre:1,pine:1,pinf:1}},c:[hljs.CBLCLM,{cN:"comment",b:";",e:"$"},hljs.CNM,hljs.BINARY_NUMBER_MODE,{cN:"number",b:"\\b(\\$[a-zA-Z0-9]+|0o[0-7]+)"},hljs.QSM,{cN:"string",b:"'",e:"[^\\\\]'",i:"[^\\\\][^']"},{cN:"label",b:"^[A-Za-z0-9_.$]+:"},{cN:"preprocessor",b:"#",e:"$"},{cN:"preprocessor",b:"\\.[a-zA-Z]+"},{cN:"localvars",b:"@[0-9]+"}]}};hljs.LANGUAGES.vhdl={cI:true,dM:{k:{keyword:{abs:1,access:1,after:1,alias:1,all:1,and:1,architecture:2,array:1,assert:1,attribute:1,begin:1,block:1,body:1,buffer:1,bus:1,"case":1,component:2,configuration:1,constant:1,disconnect:2,downto:2,"else":1,elsif:1,end:1,entity:2,exit:1,file:1,"for":1,"function":1,generate:2,generic:2,group:1,guarded:2,"if":0,impure:2,"in":1,inertial:1,inout:1,is:1,label:1,library:1,linkage:1,literal:1,loop:1,map:1,mod:1,nand:1,"new":1,next:1,nor:1,not:1,"null":1,of:1,on:1,open:1,or:1,others:1,out:1,"package":1,port:2,postponed:1,procedure:1,process:1,pure:2,range:1,record:1,register:1,reject:1,"return":1,rol:1,ror:1,select:1,severity:1,signal:1,shared:1,sla:1,sli:1,sra:1,srl:1,subtype:2,then:1,to:1,transport:1,type:1,units:1,until:1,use:1,variable:1,wait:1,when:1,"while":1,"with":1,xnor:1,xor:1},type:{"boolean":1,bit:1,character:1,severity_level:2,integer:1,time:1,delay_length:2,natural:1,positive:1,string:1,bit_vector:2,file_open_kind:2,file_open_status:2,std_ulogic:2,std_ulogic_vector:2,std_logic:2,std_logic_vector:2}},i:"{",c:[{cN:"comment",b:"--",e:"$"},hljs.QSM,hljs.CNM,{cN:"literal",b:"'(U|X|0|1|Z|W|L|H|-)",e:"'",c:[hljs.BE]}]}};hljs.LANGUAGES.coffeescript=function(){var d={keyword:{"in":1,"if":1,"for":1,"while":1,"finally":1,"new":1,"do":1,"return":1,"else":1,"break":1,"catch":1,"instanceof":1,"throw":1,"try":1,"this":1,"switch":1,"continue":1,"typeof":1,"delete":1,"debugger":1,"class":1,"extends":1,"super":1,then:1,unless:1,until:1,loop:2,of:2,by:1,when:2,and:1,or:1,is:1,isnt:2,not:1},literal:{"true":1,"false":1,"null":1,"undefined":1,yes:1,no:1,on:1,off:1},reserved:{"case":1,"default":1,"function":1,"var":1,"void":1,"with":1,"const":1,let:1,"enum":1,"export":1,"import":1,"native":1,__hasProp:1,__extends:1,__slice:1,__bind:1,__indexOf:1}};var a="[A-Za-z$_][0-9A-Za-z$_]*";var b={cN:"subst",b:"#\\{",e:"}",k:d,c:[hljs.CNM,hljs.BINARY_NUMBER_MODE]};var c={cN:"string",b:'"',e:'"',r:0,c:[hljs.BE,b]};var h={cN:"string",b:'"""',e:'"""',c:[hljs.BE,b]};var g={cN:"comment",b:"###",e:"###"};var f={cN:"regexp",b:"///",e:"///",c:[hljs.HCM]};var i={cN:"function",b:a+"\\s*=\\s*(\\(.+\\))?\\s*[-=]>",rB:true,c:[{cN:"title",b:a},{cN:"params",b:"\\(",e:"\\)"}]};var e={cN:"javascript",b:"`",e:"`",eB:true,eE:true,sL:"javascript"};return{dM:{k:d,c:[hljs.CNM,hljs.BINARY_NUMBER_MODE,hljs.ASM,h,c,g,hljs.HCM,f,e,i]}}}();hljs.LANGUAGES.nginx=function(){var c={cN:"variable",b:"\\$\\d+"};var b={cN:"variable",b:"\\${",e:"}"};var a={cN:"variable",b:"[\\$\\@]"+hljs.UIR};return{dM:{c:[hljs.HCM,{b:hljs.UIR,e:";|{",rE:true,k:{accept_mutex:1,accept_mutex_delay:1,access_log:1,add_after_body:1,add_before_body:1,add_header:1,addition_types:1,alias:1,allow:1,ancient_browser:1,ancient_browser_value:1,auth_basic:1,auth_basic_user_file:1,autoindex:1,autoindex_exact_size:1,autoindex_localtime:1,"break":1,charset:1,charset_map:1,charset_types:1,client_body_buffer_size:1,client_body_in_file_only:1,client_body_in_single_buffer:1,client_body_temp_path:1,client_body_timeout:1,client_header_buffer_size:1,client_header_timeout:1,client_max_body_size:1,connection_pool_size:1,connections:1,create_full_put_path:1,daemon:1,dav_access:1,dav_methods:1,debug_connection:1,debug_points:1,default_type:1,deny:1,directio:1,directio_alignment:1,echo:1,echo_after_body:1,echo_before_body:1,echo_blocking_sleep:1,echo_duplicate:1,echo_end:1,echo_exec:1,echo_flush:1,echo_foreach_split:1,echo_location:1,echo_location_async:1,echo_read_request_body:1,echo_request_body:1,echo_reset_timer:1,echo_sleep:1,echo_subrequest:1,echo_subrequest_async:1,empty_gif:1,env:1,error_log:1,error_page:1,events:1,expires:1,fastcgi_bind:1,fastcgi_buffer_size:1,fastcgi_buffers:1,fastcgi_busy_buffers_size:1,fastcgi_cache:1,fastcgi_cache_key:1,fastcgi_cache_methods:1,fastcgi_cache_min_uses:1,fastcgi_cache_path:1,fastcgi_cache_use_stale:1,fastcgi_cache_valid:1,fastcgi_catch_stderr:1,fastcgi_connect_timeout:1,fastcgi_hide_header:1,fastcgi_ignore_client_abort:1,fastcgi_ignore_headers:1,fastcgi_index:1,fastcgi_intercept_errors:1,fastcgi_max_temp_file_size:1,fastcgi_next_upstream:1,fastcgi_param:1,fastcgi_pass:1,fastcgi_pass_header:1,fastcgi_pass_request_body:1,fastcgi_pass_request_headers:1,fastcgi_read_timeout:1,fastcgi_send_lowat:1,fastcgi_send_timeout:1,fastcgi_split_path_info:1,fastcgi_store:1,fastcgi_store_access:1,fastcgi_temp_file_write_size:1,fastcgi_temp_path:1,fastcgi_upstream_fail_timeout:1,fastcgi_upstream_max_fails:1,flv:1,geo:1,geoip_city:1,geoip_country:1,gzip:1,gzip_buffers:1,gzip_comp_level:1,gzip_disable:1,gzip_hash:1,gzip_http_version:1,gzip_min_length:1,gzip_no_buffer:1,gzip_proxied:1,gzip_static:1,gzip_types:1,gzip_vary:1,gzip_window:1,http:1,"if":1,if_modified_since:1,ignore_invalid_headers:1,image_filter:1,image_filter_buffer:1,image_filter_jpeg_quality:1,image_filter_transparency:1,include:1,index:1,internal:1,ip_hash:1,js:1,js_load:1,js_require:1,js_utf8:1,keepalive_requests:1,keepalive_timeout:1,kqueue_changes:1,kqueue_events:1,large_client_header_buffers:1,limit_conn:1,limit_conn_log_level:1,limit_except:1,limit_rate:1,limit_rate_after:1,limit_req:1,limit_req_log_level:1,limit_req_zone:1,limit_zone:1,lingering_time:1,lingering_timeout:1,listen:1,location:1,lock_file:1,log_format:1,log_not_found:1,log_subrequest:1,map:1,map_hash_bucket_size:1,map_hash_max_size:1,master_process:1,memcached_bind:1,memcached_buffer_size:1,memcached_connect_timeout:1,memcached_next_upstream:1,memcached_pass:1,memcached_read_timeout:1,memcached_send_timeout:1,memcached_upstream_fail_timeout:1,memcached_upstream_max_fails:1,merge_slashes:1,min_delete_depth:1,modern_browser:1,modern_browser_value:1,more_clear_headers:1,more_clear_input_headers:1,more_set_headers:1,more_set_input_headers:1,msie_padding:1,msie_refresh:1,multi_accept:1,open_file_cache:1,open_file_cache_errors:1,open_file_cache_events:1,open_file_cache_min_uses:1,open_file_cache_retest:1,open_file_cache_valid:1,open_log_file_cache:1,optimize_server_names:1,output_buffers:1,override_charset:1,perl:1,perl_modules:1,perl_require:1,perl_set:1,pid:1,port_in_redirect:1,post_action:1,postpone_gzipping:1,postpone_output:1,proxy_bind:1,proxy_buffer_size:1,proxy_buffering:1,proxy_buffers:1,proxy_busy_buffers_size:1,proxy_cache:1,proxy_cache_key:1,proxy_cache_methods:1,proxy_cache_min_uses:1,proxy_cache_path:1,proxy_cache_use_stale:1,proxy_cache_valid:1,proxy_connect_timeout:1,proxy_headers_hash_bucket_size:1,proxy_headers_hash_max_size:1,proxy_hide_header:1,proxy_ignore_client_abort:1,proxy_ignore_headers:1,proxy_intercept_errors:1,proxy_max_temp_file_size:1,proxy_method:1,proxy_next_upstream:1,proxy_pass:1,proxy_pass_header:1,proxy_pass_request_body:1,proxy_pass_request_headers:1,proxy_read_timeout:1,proxy_redirect:1,proxy_send_lowat:1,proxy_send_timeout:1,proxy_set_body:1,proxy_set_header:1,proxy_store:1,proxy_store_access:1,proxy_temp_file_write_size:1,proxy_temp_path:1,proxy_upstream_fail_timeout:1,proxy_upstream_max_fails:1,push_authorized_channels_only:1,push_channel_group:1,push_max_channel_id_length:1,push_max_channel_subscribers:1,push_max_message_buffer_length:1,push_max_reserved_memory:1,push_message_buffer_length:1,push_message_timeout:1,push_min_message_buffer_length:1,push_min_message_recipients:1,push_publisher:1,push_store_messages:1,push_subscriber:1,push_subscriber_concurrency:1,random_index:1,read_ahead:1,real_ip_header:1,recursive_error_pages:1,request_pool_size:1,reset_timedout_connection:1,resolver:1,resolver_timeout:1,"return":1,rewrite:1,rewrite_log:1,root:1,satisfy:1,satisfy_any:1,send_lowat:1,send_timeout:1,sendfile:1,sendfile_max_chunk:1,server:1,server_name:1,server_name_in_redirect:1,server_names_hash_bucket_size:1,server_names_hash_max_size:1,server_tokens:1,set:1,set_real_ip_from:1,source_charset:1,ssi:1,ssi_ignore_recycled_buffers:1,ssi_min_file_chunk:1,ssi_silent_errors:1,ssi_types:1,ssi_value_length:1,ssl:1,ssl_certificate:1,ssl_certificate_key:1,ssl_ciphers:1,ssl_client_certificate:1,ssl_crl:1,ssl_dhparam:1,ssl_prefer_server_ciphers:1,ssl_protocols:1,ssl_session_cache:1,ssl_session_timeout:1,ssl_verify_client:1,ssl_verify_depth:1,sub_filter:1,sub_filter_once:1,sub_filter_types:1,tcp_nodelay:1,tcp_nopush:1,timer_resolution:1,try_files:1,types:1,types_hash_bucket_size:1,types_hash_max_size:1,underscores_in_headers:1,uninitialized_variable_warn:1,upstream:1,use:1,user:1,userid:1,userid_domain:1,userid_expires:1,userid_mark:1,userid_name:1,userid_p3p:1,userid_path:1,userid_service:1,valid_referers:1,variables_hash_bucket_size:1,variables_hash_max_size:1,worker_connections:1,worker_cpu_affinity:1,worker_priority:1,worker_processes:1,worker_rlimit_core:1,worker_rlimit_nofile:1,worker_rlimit_sigpending:1,working_directory:1,xml_entities:1,xslt_stylesheet:1,xslt_types:1},r:0,c:[hljs.HCM,{b:"\\s",e:"[;{]",rB:true,rE:true,l:"[a-z/]+",k:{built_in:{on:1,off:1,yes:1,no:1,"true":1,"false":1,none:1,blocked:1,debug:1,info:1,notice:1,warn:1,error:1,crit:1,select:1,permanent:1,redirect:1,kqueue:1,rtsig:1,epoll:1,poll:1,"/dev/poll":1}},r:0,c:[hljs.HCM,{cN:"string",b:'"',e:'"',c:[hljs.BE,c,b,a],r:0},{cN:"string",b:"'",e:"'",c:[hljs.BE,c,b,a],r:0},{cN:"string",b:"([a-z]+):/",e:"[;\\s]",rE:true},{cN:"regexp",b:"\\s\\^",e:"\\s|{|;",rE:true,c:[hljs.BE,c,b,a]},{cN:"regexp",b:"~\\*?\\s+",e:"\\s|{|;",rE:true,c:[hljs.BE,c,b,a]},{cN:"regexp",b:"\\*(\\.[a-z\\-]+)+",c:[hljs.BE,c,b,a]},{cN:"regexp",b:"([a-z\\-]+\\.)+\\*",c:[hljs.BE,c,b,a]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b"},{cN:"number",b:"\\s\\d+[kKmMgGdshdwy]*\\b",r:0},c,b,a]}]}]}}}();hljs.LANGUAGES.erlang_repl={dM:{k:{special_functions:{spawn:10,spawn_link:10,self:2},reserved:{after:1,and:1,andalso:5,band:1,begin:1,bnot:1,bor:1,bsl:1,bsr:1,bxor:1,"case":1,"catch":0,cond:1,div:1,end:1,fun:0,"if":0,let:1,not:0,of:1,or:1,orelse:5,query:1,receive:0,rem:1,"try":0,when:1,xor:1}},c:[{cN:"input_number",b:"^[0-9]+> ",r:10},{cN:"comment",b:"%",e:"$"},{cN:"number",b:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",r:0},hljs.ASM,hljs.QSM,{cN:"constant",b:"\\?(::)?([A-Z]\\w*(::)?)+"},{cN:"arrow",b:"->"},{cN:"ok",b:"ok"},{cN:"exclamation_mark",b:"!"},{cN:"function_or_atom",b:"(\\b[a-z'][a-zA-Z0-9_']*:[a-z'][a-zA-Z0-9_']*)|(\\b[a-z'][a-zA-Z0-9_']*)",r:0},{cN:"variable",b:"[A-Z][a-zA-Z0-9_']*",r:0}]}};hljs.LANGUAGES.django=function(){function c(f,e){return(e==undefined||(!f.cN&&e.cN=="tag")||f.cN=="value")}function d(j,e){var h={};for(var g in j){if(g!="contains"){h[g]=j[g]}var k=[];for(var f=0;j.c&&f"},hljs.QSM]}}}();hljs.LANGUAGES.cpp=function(){var a={keyword:{"false":1,"int":1,"float":1,"while":1,"private":1,"char":1,"catch":1,"export":1,virtual:1,operator:2,sizeof:2,dynamic_cast:2,typedef:2,const_cast:2,"const":1,struct:1,"for":1,static_cast:2,union:1,namespace:1,unsigned:1,"long":1,"throw":1,"volatile":2,"static":1,"protected":1,bool:1,template:1,mutable:1,"if":1,"public":1,friend:2,"do":1,"return":1,"goto":1,auto:1,"void":2,"enum":1,"else":1,"break":1,"new":1,extern:1,using:1,"true":1,"class":1,asm:1,"case":1,typeid:1,"short":1,reinterpret_cast:2,"default":1,"double":1,register:1,explicit:1,signed:1,typename:1,"try":1,"this":1,"switch":1,"continue":1,wchar_t:1,inline:1,"delete":1,alignof:1,char16_t:1,char32_t:1,constexpr:1,decltype:1,noexcept:1,nullptr:1,static_assert:1,thread_local:1,restrict:1,_Bool:1,complex:1},built_in:{std:1,string:1,cin:1,cout:1,cerr:1,clog:1,stringstream:1,istringstream:1,ostringstream:1,auto_ptr:1,deque:1,list:1,queue:1,stack:1,vector:1,map:1,set:1,bitset:1,multiset:1,multimap:1,unordered_set:1,unordered_map:1,unordered_multiset:1,unordered_multimap:1,array:1,shared_ptr:1}};return{dM:{k:a,i:"",k:a,r:10,c:["self"]}]}}}();hljs.LANGUAGES.matlab={dM:{k:{keyword:{"break":1,"case":1,"catch":1,classdef:1,"continue":1,"else":1,elseif:1,end:1,enumerated:1,events:1,"for":1,"function":1,global:1,"if":1,methods:1,otherwise:1,parfor:1,persistent:1,properties:1,"return":1,spmd:1,"switch":1,"try":1,"while":1},built_in:{sin:1,sind:1,sinh:1,asin:1,asind:1,asinh:1,cos:1,cosd:1,cosh:1,acos:1,acosd:1,acosh:1,tan:1,tand:1,tanh:1,atan:1,atand:1,atan2:1,atanh:1,sec:1,secd:1,sech:1,asec:1,asecd:1,asech:1,csc:1,cscd:1,csch:1,acsc:1,acscd:1,acsch:1,cot:1,cotd:1,coth:1,acot:1,acotd:1,acoth:1,hypot:1,exp:1,expm1:1,log:1,log1p:1,log10:1,log2:1,pow2:1,realpow:1,reallog:1,realsqrt:1,sqrt:1,nthroot:1,nextpow2:1,abs:1,angle:1,complex:1,conj:1,imag:1,real:1,unwrap:1,isreal:1,cplxpair:1,fix:1,floor:1,ceil:1,round:1,mod:1,rem:1,sign:1,airy:1,besselj:1,bessely:1,besselh:1,besseli:1,besselk:1,beta:1,betainc:1,betaln:1,ellipj:1,ellipke:1,erf:1,erfc:1,erfcx:1,erfinv:1,expint:1,gamma:1,gammainc:1,gammaln:1,psi:1,legendre:1,cross:1,dot:1,factor:1,isprime:1,primes:1,gcd:1,lcm:1,rat:1,rats:1,perms:1,nchoosek:1,factorial:1,cart2sph:1,cart2pol:1,pol2cart:1,sph2cart:1,hsv2rgb:1,rgb2hsv:1,zeros:1,ones:1,eye:1,repmat:1,rand:1,randn:1,linspace:1,logspace:1,freqspace:1,meshgrid:1,accumarray:1,size:1,length:1,ndims:1,numel:1,disp:1,isempty:1,isequal:1,isequalwithequalnans:1,cat:1,reshape:1,diag:1,blkdiag:1,tril:1,triu:1,fliplr:1,flipud:1,flipdim:1,rot90:1,find:1,end:1,sub2ind:1,ind2sub:1,bsxfun:1,ndgrid:1,permute:1,ipermute:1,shiftdim:1,circshift:1,squeeze:1,isscalar:1,isvector:1,ans:1,eps:1,realmax:1,realmin:1,pi:1,i:1,inf:1,nan:1,isnan:1,isinf:1,isfinite:1,j:1,why:1,compan:1,gallery:1,hadamard:1,hankel:1,hilb:1,invhilb:1,magic:1,pascal:1,rosser:1,toeplitz:1,vander:1,wilkinson:1},},i:'(//|"|#|/\\*|\\s+/\\w+)',c:[{cN:"function",b:"function",e:"$",k:{"function":1},c:[{cN:"title",b:hljs.UIR},{cN:"params",b:"\\(",e:"\\)"},{cN:"params",b:"\\[",e:"\\]"}]},{cN:"string",b:"'",e:"'",c:[hljs.BE,{b:"''"}]},{cN:"comment",b:"\\%",e:"$"},hljs.CNM]}};hljs.LANGUAGES.parser3={dM:{sL:"html",c:[{cN:"comment",b:"^#",e:"$"},{cN:"comment",b:"\\^rem{",e:"}",r:10,c:[{b:"{",e:"}",c:["self"]}]},{cN:"preprocessor",b:"^@(?:BASE|USE|CLASS|OPTIONS)$",r:10},{cN:"title",b:"@[\\w\\-]+\\[[\\w^;\\-]*\\](?:\\[[\\w^;\\-]*\\])?(?:.*)$"},{cN:"variable",b:"\\$\\{?[\\w\\-\\.\\:]+\\}?"},{cN:"keyword",b:"\\^[\\w\\-\\.\\:]+"},{cN:"number",b:"\\^#[0-9a-fA-F]+"},hljs.CNM]}};hljs.LANGUAGES.go=function(){var a={keyword:{"break":1,"default":1,func:1,"interface":1,select:1,"case":1,map:1,struct:1,chan:1,"else":1,"goto":1,"package":1,"switch":1,"const":1,fallthrough:1,"if":1,range:1,type:1,"continue":1,"for":1,"import":1,"return":1,"var":1,go:1,defer:1},constant:{"true":1,"false":1,iota:1,nil:1},typename:{bool:1,"byte":1,complex64:1,complex128:1,float32:1,float64:1,int8:1,int16:1,int32:1,int64:1,string:1,uint8:1,uint16:1,uint32:1,uint64:1,"int":1,uint:1,uintptr:1,rune:1},built_in:{append:1,cap:1,close:1,complex:1,copy:1,imag:1,len:1,make:1,"new":1,panic:1,print:1,println:1,real:1,recover:1,"delete":1}};return{dM:{k:a,i:" +Contributors: Sergey Baranov +*/ + +hljs.LANGUAGES['1c'] = function(){ + var IDENT_RE_RU = '[a-zA-Zа-яА-Я][a-zA-Z0-9_а-яА-Я]*'; + var OneS_KEYWORDS = { + 'возврат':1,'дата':1,'для':1,'если':1,'и':1,'или':1,'иначе':1,'иначеесли':1,'исключение':1,'конецесли':1, + 'конецпопытки':1,'конецпроцедуры':1,'конецфункции':1,'конеццикла':1,'константа':1,'не':1,'перейти':1,'перем':1, + 'перечисление':1,'по':1,'пока':1,'попытка':1,'прервать':1,'продолжить':1,'процедура':1,'строка':1,'тогда':1, + 'фс':1,'функция':1,'цикл':1,'число':1,'экспорт':1 + }; + var OneS_BUILT_IN = { + 'ansitooem':1,'oemtoansi':1,'ввестивидсубконто':1,'ввестидату':1,'ввестизначение':1,'ввестиперечисление':1, + 'ввестипериод':1,'ввестиплансчетов':1,'ввестистроку':1,'ввестичисло':1,'вопрос':1,'восстановитьзначение':1, + 'врег':1,'выбранныйплансчетов':1,'вызватьисключение':1,'датагод':1,'датамесяц':1,'датачисло':1,'добавитьмесяц':1, + 'завершитьработусистемы':1,'заголовоксистемы':1,'записьжурналарегистрации':1,'запуститьприложение':1, + 'зафиксироватьтранзакцию':1,'значениевстроку':1,'значениевстрокувнутр':1,'значениевфайл':1,'значениеизстроки':1, + 'значениеизстрокивнутр':1,'значениеизфайла':1,'имякомпьютера':1,'имяпользователя':1,'каталогвременныхфайлов':1, + 'каталогиб':1,'каталогпользователя':1,'каталогпрограммы':1,'кодсимв':1,'командасистемы':1,'конгода':1, + 'конецпериодаби':1,'конецрассчитанногопериодаби':1,'конецстандартногоинтервала':1,'конквартала':1,'конмесяца':1, + 'коннедели':1,'лев':1,'лог':1,'лог10':1,'макс':1,'максимальноеколичествосубконто':1,'мин':1,'монопольныйрежим':1, + 'названиеинтерфейса':1,'названиенабораправ':1,'назначитьвид':1,'назначитьсчет':1,'найти':1, + 'найтипомеченныенаудаление':1,'найтиссылки':1,'началопериодаби':1,'началостандартногоинтервала':1, + 'начатьтранзакцию':1,'начгода':1,'начквартала':1,'начмесяца':1,'начнедели':1,'номерднягода':1,'номерднянедели':1, + 'номернеделигода':1,'нрег':1,'обработкаожидания':1,'окр':1,'описаниеошибки':1,'основнойжурналрасчетов':1, + 'основнойплансчетов':1,'основнойязык':1,'открытьформу':1,'открытьформумодально':1,'отменитьтранзакцию':1, + 'очиститьокносообщений':1,'периодстр':1,'полноеимяпользователя':1,'получитьвремята':1,'получитьдатута':1, + 'получитьдокументта':1,'получитьзначенияотбора':1,'получитьпозициюта':1,'получитьпустоезначение':1, + 'получитьта':1,'прав':1,'праводоступа':1,'предупреждение':1,'префиксавтонумерации':1,'пустаястрока':1, + 'пустоезначение':1,'рабочаядаттьпустоезначение':1,'рабочаядата':1,'разделительстраниц':1,'разделительстрок':1, + 'разм':1,'разобратьпозициюдокумента':1,'рассчитатьрегистрына':1,'рассчитатьрегистрыпо':1,'сигнал':1,'симв':1, + 'символтабуляции':1,'создатьобъект':1,'сокрл':1,'сокрлп':1,'сокрп':1,' сообщить':1,'состояние':1, + 'сохранитьзначение':1,'сред':1,'статусвозврата':1,'стрдлина':1,'стрзаменить':1,'стрколичествострок':1, + 'стрполучитьстроку':1,' стрчисловхождений':1,'сформироватьпозициюдокумента':1,'счетпокоду':1,'текущаядата':1, + 'текущеевремя':1,'типзначения':1,'типзначениястр':1,'удалитьобъекты':1,'установитьтана':1,'установитьтапо':1, + 'фиксшаблон':1,'формат':1,'цел':1,'шаблон':1 + }; + var DQUOTE = {className: 'dquote', begin: '""'}; + var STR_START = { + className: 'string', + begin: '"', end: '"|$', + contains: [DQUOTE], + relevance: 0 + }; + var STR_CONT = { + className: 'string', + begin: '\\|', end: '"|$', + contains: [DQUOTE] + }; + + return { + case_insensitive: true, + defaultMode: { + lexems: IDENT_RE_RU, + keywords: {'keyword':OneS_KEYWORDS,'built_in':OneS_BUILT_IN}, + contains: [ + hljs.C_LINE_COMMENT_MODE, + hljs.NUMBER_MODE, + STR_START, STR_CONT, + { + className: 'function', + begin: '(процедура|функция)', end: '$', + lexems: IDENT_RE_RU, + keywords: {'процедура': 1, 'экспорт': 1, 'функция': 1}, + contains: [ + {className: 'title', begin: IDENT_RE_RU}, + { + className: 'tail', + endsWithParent: true, + contains: [ + { + className: 'params', + begin: '\\(', end: '\\)', + lexems: IDENT_RE_RU, + keywords: {'знач':1}, + contains: [STR_START, STR_CONT] + }, + { + className: 'export', + begin: 'экспорт', endsWithParent: true, + lexems: IDENT_RE_RU, + keywords: {'экспорт': 1}, + contains: [hljs.C_LINE_COMMENT_MODE] + } + ] + }, + hljs.C_LINE_COMMENT_MODE + ] + }, + {className: 'preprocessor', begin: '#', end: '$'}, + {className: 'date', begin: '\'\\d{2}\\.\\d{2}\\.(\\d{2}|\\d{4})\''} + ] + } + }; +}(); diff --git a/Scratch/js/highlight/languages/actionscript.js b/Scratch/js/highlight/languages/actionscript.js new file mode 100644 index 0000000..255c69c --- /dev/null +++ b/Scratch/js/highlight/languages/actionscript.js @@ -0,0 +1,94 @@ +/* +Language: ActionScript +Author: Alexander Myadzel +*/ + +hljs.LANGUAGES.actionscript = function() { + var IDENT_RE = '[a-zA-Z_$][a-zA-Z0-9_$]*'; + var IDENT_FUNC_RETURN_TYPE_RE = '([*]|[a-zA-Z_$][a-zA-Z0-9_$]*)'; + + var AS3_REST_ARG_MODE = { + className: 'rest_arg', + begin: '[.]{3}', end: IDENT_RE, + relevance: 10 + }; + var TITLE_MODE = {className: 'title', begin: IDENT_RE}; + + return { + defaultMode: { + keywords: { + 'keyword': { + 'as': 1, 'break': 1, 'case': 1, 'catch': 1, 'class': 1, 'const': 1, 'continue': 1, 'default': 1, + 'delete': 1, 'do': 1, 'dynamic': 5, 'each': 1, 'else': 1, 'extends': 1, 'final': 1, 'finally': 1, + 'for': 1, 'function': 1, 'get': 1, 'if': 1, 'implements': 1, 'import': 1, 'in': 1, 'include': 1, + 'instanceof': 1, 'interface': 1, 'internal': 1, 'is': 1, 'namespace': 1, 'native': 1, 'new': 1, + 'override': 1, 'package': 1, 'private': 1, 'protected': 1, 'public': 1, 'return': 1, 'set': 1, + 'static': 1, 'super': 5, 'switch': 1, 'this': 1, 'throw': 1, 'try': 1, 'typeof': 1, 'use': 1, + 'var': 1, 'void': 1, 'while': 1, 'with': 1 + }, + 'literal': {'true': 1, 'false': 1, 'null': 1, 'undefined': 1}, + 'reserved': { + 'abstract': 0, 'boolean': 0, 'byte': 0, 'cast': 0, 'char': 0, 'debugger': 0, 'double': 0, 'enum': 0, + 'export': 0, 'float': 0, 'goto': 0, 'intrinsic': 0, 'long': 0, 'prototype': 0, 'short': 0, + 'synchronized': 0, 'throws': 0, 'to': 0, 'transient': 0, 'type': 0, 'virtual': 0, 'volatile': 0 + } + }, + contains: [ + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE, + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + hljs.C_NUMBER_MODE, + { + className: 'package', + begin: 'package ?', end: '{', + keywords: {'package': 1}, + contains: [TITLE_MODE] + }, + { + className: 'class', + begin: '(class|interface) ', end: '{', + keywords: {'class': 1, 'interface': 1}, + contains: [ + { + begin: '(implements|extends)', + keywords: {'extends': 1, 'implements': 1}, + relevance: 5 + }, + TITLE_MODE + ] + }, + { + className: 'preprocessor', + begin: '(import|include)\\b', end: ';', + keywords: {'import': 1, 'include': 1} + }, + { + className: 'function', + begin: 'function ', end: '[{;]', + keywords: {'function': 1}, + contains: [ + TITLE_MODE, + { + className: 'params', + begin: '\\(', end: '\\)', + contains: [ + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE, + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + AS3_REST_ARG_MODE + ] + }, + { + className: 'type', + begin: ':', + end: IDENT_FUNC_RETURN_TYPE_RE, + relevance: 10 + } + ] + } + ] + } + } +}(); diff --git a/Scratch/js/highlight/languages/apache.js b/Scratch/js/highlight/languages/apache.js new file mode 100644 index 0000000..d6bdfe3 --- /dev/null +++ b/Scratch/js/highlight/languages/apache.js @@ -0,0 +1,434 @@ +/* +Language: Apache +Author: Ruslan Keba +Website: http://rukeba.com/ +Description: language definition for Apache configuration files (httpd.conf & .htaccess) +Version: 1.1 +Date: 2008-12-27 +*/ + +hljs.LANGUAGES.apache = function(){ + var NUMBER = {className: 'number', begin: '[\\$%]\\d+'}; + return { + case_insensitive: true, + defaultMode: { + keywords: { + 'keyword': { + 'acceptfilter': 1, + 'acceptmutex': 1, + 'acceptpathinfo': 1, + 'accessfilename': 1, + 'action': 1, + 'addalt': 1, + 'addaltbyencoding': 1, + 'addaltbytype': 1, + 'addcharset': 1, + 'adddefaultcharset': 1, + 'adddescription': 1, + 'addencoding': 1, + 'addhandler': 1, + 'addicon': 1, + 'addiconbyencoding': 1, + 'addiconbytype': 1, + 'addinputfilter': 1, + 'addlanguage': 1, + 'addmoduleinfo': 1, + 'addoutputfilter': 1, + 'addoutputfilterbytype': 1, + 'addtype': 1, + 'alias': 1, + 'aliasmatch': 1, + 'allow': 1, + 'allowconnect': 1, + 'allowencodedslashes': 1, + 'allowoverride': 1, + 'anonymous': 1, + 'anonymous_logemail': 1, + 'anonymous_mustgiveemail': 1, + 'anonymous_nouserid': 1, + 'anonymous_verifyemail': 1, + 'authbasicauthoritative': 1, + 'authbasicprovider': 1, + 'authdbduserpwquery': 1, + 'authdbduserrealmquery': 1, + 'authdbmgroupfile': 1, + 'authdbmtype': 1, + 'authdbmuserfile': 1, + 'authdefaultauthoritative': 1, + 'authdigestalgorithm': 1, + 'authdigestdomain': 1, + 'authdigestnccheck': 1, + 'authdigestnonceformat': 1, + 'authdigestnoncelifetime': 1, + 'authdigestprovider': 1, + 'authdigestqop': 1, + 'authdigestshmemsize': 1, + 'authgroupfile': 1, + 'authldapbinddn': 1, + 'authldapbindpassword': 1, + 'authldapcharsetconfig': 1, + 'authldapcomparednonserver': 1, + 'authldapdereferencealiases': 1, + 'authldapgroupattribute': 1, + 'authldapgroupattributeisdn': 1, + 'authldapremoteuserattribute': 1, + 'authldapremoteuserisdn': 1, + 'authldapurl': 1, + 'authname': 1, + 'authnprovideralias': 1, + 'authtype': 1, + 'authuserfile': 1, + 'authzdbmauthoritative': 1, + 'authzdbmtype': 1, + 'authzdefaultauthoritative': 1, + 'authzgroupfileauthoritative': 1, + 'authzldapauthoritative': 1, + 'authzownerauthoritative': 1, + 'authzuserauthoritative': 1, + 'balancermember': 1, + 'browsermatch': 1, + 'browsermatchnocase': 1, + 'bufferedlogs': 1, + 'cachedefaultexpire': 1, + 'cachedirlength': 1, + 'cachedirlevels': 1, + 'cachedisable': 1, + 'cacheenable': 1, + 'cachefile': 1, + 'cacheignorecachecontrol': 1, + 'cacheignoreheaders': 1, + 'cacheignorenolastmod': 1, + 'cacheignorequerystring': 1, + 'cachelastmodifiedfactor': 1, + 'cachemaxexpire': 1, + 'cachemaxfilesize': 1, + 'cacheminfilesize': 1, + 'cachenegotiateddocs': 1, + 'cacheroot': 1, + 'cachestorenostore': 1, + 'cachestoreprivate': 1, + 'cgimapextension': 1, + 'charsetdefault': 1, + 'charsetoptions': 1, + 'charsetsourceenc': 1, + 'checkcaseonly': 1, + 'checkspelling': 1, + 'chrootdir': 1, + 'contentdigest': 1, + 'cookiedomain': 1, + 'cookieexpires': 1, + 'cookielog': 1, + 'cookiename': 1, + 'cookiestyle': 1, + 'cookietracking': 1, + 'coredumpdirectory': 1, + 'customlog': 1, + 'dav': 1, + 'davdepthinfinity': 1, + 'davgenericlockdb': 1, + 'davlockdb': 1, + 'davmintimeout': 1, + 'dbdexptime': 1, + 'dbdkeep': 1, + 'dbdmax': 1, + 'dbdmin': 1, + 'dbdparams': 1, + 'dbdpersist': 1, + 'dbdpreparesql': 1, + 'dbdriver': 1, + 'defaulticon': 1, + 'defaultlanguage': 1, + 'defaulttype': 1, + 'deflatebuffersize': 1, + 'deflatecompressionlevel': 1, + 'deflatefilternote': 1, + 'deflatememlevel': 1, + 'deflatewindowsize': 1, + 'deny': 1, + 'directoryindex': 1, + 'directorymatch': 1, + 'directoryslash': 1, + 'documentroot': 1, + 'dumpioinput': 1, + 'dumpiologlevel': 1, + 'dumpiooutput': 1, + 'enableexceptionhook': 1, + 'enablemmap': 1, + 'enablesendfile': 1, + 'errordocument': 1, + 'errorlog': 1, + 'example': 1, + 'expiresactive': 1, + 'expiresbytype': 1, + 'expiresdefault': 1, + 'extendedstatus': 1, + 'extfilterdefine': 1, + 'extfilteroptions': 1, + 'fileetag': 1, + 'filterchain': 1, + 'filterdeclare': 1, + 'filterprotocol': 1, + 'filterprovider': 1, + 'filtertrace': 1, + 'forcelanguagepriority': 1, + 'forcetype': 1, + 'forensiclog': 1, + 'gracefulshutdowntimeout': 1, + 'group': 1, + 'header': 1, + 'headername': 1, + 'hostnamelookups': 1, + 'identitycheck': 1, + 'identitychecktimeout': 1, + 'imapbase': 1, + 'imapdefault': 1, + 'imapmenu': 1, + 'include': 1, + 'indexheadinsert': 1, + 'indexignore': 1, + 'indexoptions': 1, + 'indexorderdefault': 1, + 'indexstylesheet': 1, + 'isapiappendlogtoerrors': 1, + 'isapiappendlogtoquery': 1, + 'isapicachefile': 1, + 'isapifakeasync': 1, + 'isapilognotsupported': 1, + 'isapireadaheadbuffer': 1, + 'keepalive': 1, + 'keepalivetimeout': 1, + 'languagepriority': 1, + 'ldapcacheentries': 1, + 'ldapcachettl': 1, + 'ldapconnectiontimeout': 1, + 'ldapopcacheentries': 1, + 'ldapopcachettl': 1, + 'ldapsharedcachefile': 1, + 'ldapsharedcachesize': 1, + 'ldaptrustedclientcert': 1, + 'ldaptrustedglobalcert': 1, + 'ldaptrustedmode': 1, + 'ldapverifyservercert': 1, + 'limitinternalrecursion': 1, + 'limitrequestbody': 1, + 'limitrequestfields': 1, + 'limitrequestfieldsize': 1, + 'limitrequestline': 1, + 'limitxmlrequestbody': 1, + 'listen': 1, + 'listenbacklog': 1, + 'loadfile': 1, + 'loadmodule': 1, + 'lockfile': 1, + 'logformat': 1, + 'loglevel': 1, + 'maxclients': 1, + 'maxkeepaliverequests': 1, + 'maxmemfree': 1, + 'maxrequestsperchild': 1, + 'maxrequestsperthread': 1, + 'maxspareservers': 1, + 'maxsparethreads': 1, + 'maxthreads': 1, + 'mcachemaxobjectcount': 1, + 'mcachemaxobjectsize': 1, + 'mcachemaxstreamingbuffer': 1, + 'mcacheminobjectsize': 1, + 'mcacheremovalalgorithm': 1, + 'mcachesize': 1, + 'metadir': 1, + 'metafiles': 1, + 'metasuffix': 1, + 'mimemagicfile': 1, + 'minspareservers': 1, + 'minsparethreads': 1, + 'mmapfile': 1, + 'mod_gzip_on': 1, + 'mod_gzip_add_header_count': 1, + 'mod_gzip_keep_workfiles': 1, + 'mod_gzip_dechunk': 1, + 'mod_gzip_min_http': 1, + 'mod_gzip_minimum_file_size': 1, + 'mod_gzip_maximum_file_size': 1, + 'mod_gzip_maximum_inmem_size': 1, + 'mod_gzip_temp_dir': 1, + 'mod_gzip_item_include': 1, + 'mod_gzip_item_exclude': 1, + 'mod_gzip_command_version': 1, + 'mod_gzip_can_negotiate': 1, + 'mod_gzip_handle_methods': 1, + 'mod_gzip_static_suffix': 1, + 'mod_gzip_send_vary': 1, + 'mod_gzip_update_static': 1, + 'modmimeusepathinfo': 1, + 'multiviewsmatch': 1, + 'namevirtualhost': 1, + 'noproxy': 1, + 'nwssltrustedcerts': 1, + 'nwsslupgradeable': 1, + 'options': 1, + 'order': 1, + 'passenv': 1, + 'pidfile': 1, + 'protocolecho': 1, + 'proxybadheader': 1, + 'proxyblock': 1, + 'proxydomain': 1, + 'proxyerroroverride': 1, + 'proxyftpdircharset': 1, + 'proxyiobuffersize': 1, + 'proxymaxforwards': 1, + 'proxypass': 1, + 'proxypassinterpolateenv': 1, + 'proxypassmatch': 1, + 'proxypassreverse': 1, + 'proxypassreversecookiedomain': 1, + 'proxypassreversecookiepath': 1, + 'proxypreservehost': 1, + 'proxyreceivebuffersize': 1, + 'proxyremote': 1, + 'proxyremotematch': 1, + 'proxyrequests': 1, + 'proxyset': 1, + 'proxystatus': 1, + 'proxytimeout': 1, + 'proxyvia': 1, + 'readmename': 1, + 'receivebuffersize': 1, + 'redirect': 1, + 'redirectmatch': 1, + 'redirectpermanent': 1, + 'redirecttemp': 1, + 'removecharset': 1, + 'removeencoding': 1, + 'removehandler': 1, + 'removeinputfilter': 1, + 'removelanguage': 1, + 'removeoutputfilter': 1, + 'removetype': 1, + 'requestheader': 1, + 'require': 2, + 'rewritebase': 1, + 'rewritecond': 10, + 'rewriteengine': 1, + 'rewritelock': 1, + 'rewritelog': 1, + 'rewriteloglevel': 1, + 'rewritemap': 1, + 'rewriteoptions': 1, + 'rewriterule': 10, + 'rlimitcpu': 1, + 'rlimitmem': 1, + 'rlimitnproc': 1, + 'satisfy': 1, + 'scoreboardfile': 1, + 'script': 1, + 'scriptalias': 1, + 'scriptaliasmatch': 1, + 'scriptinterpretersource': 1, + 'scriptlog': 1, + 'scriptlogbuffer': 1, + 'scriptloglength': 1, + 'scriptsock': 1, + 'securelisten': 1, + 'seerequesttail': 1, + 'sendbuffersize': 1, + 'serveradmin': 1, + 'serveralias': 1, + 'serverlimit': 1, + 'servername': 1, + 'serverpath': 1, + 'serverroot': 1, + 'serversignature': 1, + 'servertokens': 1, + 'setenv': 1, + 'setenvif': 1, + 'setenvifnocase': 1, + 'sethandler': 1, + 'setinputfilter': 1, + 'setoutputfilter': 1, + 'ssienableaccess': 1, + 'ssiendtag': 1, + 'ssierrormsg': 1, + 'ssistarttag': 1, + 'ssitimeformat': 1, + 'ssiundefinedecho': 1, + 'sslcacertificatefile': 1, + 'sslcacertificatepath': 1, + 'sslcadnrequestfile': 1, + 'sslcadnrequestpath': 1, + 'sslcarevocationfile': 1, + 'sslcarevocationpath': 1, + 'sslcertificatechainfile': 1, + 'sslcertificatefile': 1, + 'sslcertificatekeyfile': 1, + 'sslciphersuite': 1, + 'sslcryptodevice': 1, + 'sslengine': 1, + 'sslhonorciperorder': 1, + 'sslmutex': 1, + 'ssloptions': 1, + 'sslpassphrasedialog': 1, + 'sslprotocol': 1, + 'sslproxycacertificatefile': 1, + 'sslproxycacertificatepath': 1, + 'sslproxycarevocationfile': 1, + 'sslproxycarevocationpath': 1, + 'sslproxyciphersuite': 1, + 'sslproxyengine': 1, + 'sslproxymachinecertificatefile': 1, + 'sslproxymachinecertificatepath': 1, + 'sslproxyprotocol': 1, + 'sslproxyverify': 1, + 'sslproxyverifydepth': 1, + 'sslrandomseed': 1, + 'sslrequire': 1, + 'sslrequiressl': 1, + 'sslsessioncache': 1, + 'sslsessioncachetimeout': 1, + 'sslusername': 1, + 'sslverifyclient': 1, + 'sslverifydepth': 1, + 'startservers': 1, + 'startthreads': 1, + 'substitute': 1, + 'suexecusergroup': 1, + 'threadlimit': 1, + 'threadsperchild': 1, + 'threadstacksize': 1, + 'timeout': 1, + 'traceenable': 1, + 'transferlog': 1, + 'typesconfig': 1, + 'unsetenv': 1, + 'usecanonicalname': 1, + 'usecanonicalphysicalport': 1, + 'user': 1, + 'userdir': 1, + 'virtualdocumentroot': 1, + 'virtualdocumentrootip': 1, + 'virtualscriptalias': 1, + 'virtualscriptaliasip': 1, + 'win32disableacceptex': 1, + 'xbithack': 1 + }, + 'literal': {'on': 1, 'off': 1} + }, + contains: [ + hljs.HASH_COMMENT_MODE, + { + className: 'sqbracket', + begin: '\\s\\[', end: '\\]$' + }, + { + className: 'cbracket', + begin: '[\\$%]\\{', end: '\\}', + contains: ['self', NUMBER] + }, + NUMBER, + {className: 'tag', begin: ''}, + hljs.QUOTE_STRING_MODE + ] + } + }; +}(); diff --git a/Scratch/js/highlight/languages/avrasm.js b/Scratch/js/highlight/languages/avrasm.js new file mode 100644 index 0000000..5e3e954 --- /dev/null +++ b/Scratch/js/highlight/languages/avrasm.js @@ -0,0 +1,75 @@ +/* +Language: AVR Assembler +Author: Vladimir Ermakov +*/ + +hljs.LANGUAGES.avrasm = +{ + case_insensitive: true, + defaultMode: { + keywords: { + 'keyword': { + /* mnemonic */ + 'adc': 1, 'add': 1 , 'adiw': 1 , 'and': 1 , 'andi': 1 , 'asr': 1 , 'bclr': 1 , 'bld': 1 , 'brbc': 1 , 'brbs': 1 , 'brcc': 1 , + 'brcs': 1, 'break': 1, 'breq': 1, 'brge': 1, 'brhc': 1, 'brhs': 1, 'brid': 1, 'brie': 1, 'brlo': 1, 'brlt': 1, 'brmi': 1, + 'brne': 1, 'brpl': 1, 'brsh': 1, 'brtc': 1, 'brts': 1, 'brvc': 1, 'brvs': 1, 'bset': 1, 'bst': 1, 'call': 1, 'cbi': 1, + 'cbr': 1, 'clc': 1, 'clh': 1, 'cli': 1, 'cln': 1, 'clr': 1, 'cls': 1, 'clt': 1, 'clv': 1, 'clz': 1, 'com': 1, 'cp': 1, + 'cpc': 1, 'cpi': 1, 'cpse': 1, 'dec': 1, 'eicall': 1, 'eijmp': 1, 'elpm': 1, 'eor': 1, 'fmul': 1, 'fmuls': 1, 'fmulsu': 1, + 'icall': 1, 'ijmp': 1, 'in': 1, 'inc': 1, 'jmp': 1, 'ld': 1, 'ldd': 1, 'ldi': 1, 'lds': 1, 'lpm': 1, 'lsl': 1, 'lsr': 1, + 'mov': 1, 'movw': 1, 'mul': 1, 'muls': 1, 'mulsu': 1, 'neg': 1, 'nop': 1, 'or': 1, 'ori': 1, 'out': 1, 'pop': 1, 'push': 1, + 'rcall': 1, 'ret': 1, 'reti': 1, 'rjmp': 1, 'rol': 1, 'ror': 1, 'sbc': 1, 'sbr': 1, 'sbrc': 1, 'sbrs': 1, 'sec': 1, 'seh': 1, + 'sbi': 1, 'sbci': 1, 'sbic': 1, 'sbis': 1, 'sbiw': 1, 'sei': 1, 'sen': 1, 'ser': 1, 'ses': 1, 'set': 1, 'sev': 1, 'sez': 1, + 'sleep': 1, 'spm': 1, 'st': 1, 'std': 1, 'sts': 1, 'sub': 1, 'subi': 1, 'swap': 1, 'tst': 1, 'wdr': 1 + }, + 'built_in': { + /* general purpose registers */ + 'r0': 1, 'r1': 1, 'r2': 1, 'r3': 1, 'r4': 1, 'r5': 1, 'r6': 1, 'r7': 1, 'r8': 1, 'r9': 1, 'r10': 1, 'r11': 1, 'r12': 1, + 'r13': 1, 'r14': 1, 'r15': 1, 'r16': 1, 'r17': 1, 'r18': 1, 'r19': 1, 'r20': 1, 'r21': 1, 'r22': 1, 'r23': 1, 'r24': 1, + 'r25': 1, 'r26': 1, 'r27': 1, 'r28': 1, 'r29': 1, 'r30': 1, 'r31': 1, + 'x': 1 /* R27:R26 */, 'xh': 1 /* R27 */, 'xl': 1 /* R26 */, + 'y': 1 /* R29:R28 */, 'yh': 1 /* R29 */, 'yl': 1 /* R28 */, + 'z': 1 /* R31:R30 */, 'zh': 1 /* R31 */, 'zl': 1 /* R30 */, + /* IO Registers (ATMega128) */ + 'ucsr1c': 1, 'udr1': 1, 'ucsr1a': 1, 'ucsr1b': 1, 'ubrr1l': 1, 'ubrr1h': 1, 'ucsr0c': 1, 'ubrr0h': 1, 'tccr3c': 1, + 'tccr3a': 1, 'tccr3b': 1, 'tcnt3h': 1, 'tcnt3l': 1, 'ocr3ah': 1, 'ocr3al': 1, 'ocr3bh': 1, 'ocr3bl': 1, 'ocr3ch': 1, + 'ocr3cl': 1, 'icr3h': 1, 'icr3l': 1, 'etimsk': 1, 'etifr': 1, 'tccr1c': 1, 'ocr1ch': 1, 'ocr1cl': 1, 'twcr': 1, + 'twdr': 1, 'twar': 1, 'twsr': 1, 'twbr': 1, 'osccal': 1, 'xmcra': 1, 'xmcrb': 1, 'eicra': 1, 'spmcsr': 1, 'spmcr': 1, + 'portg': 1, 'ddrg': 1, 'ping': 1, 'portf': 1, 'ddrf': 1, 'sreg': 1, 'sph': 1, 'spl': 1, 'xdiv': 1, 'rampz': 1, + 'eicrb': 1, 'eimsk': 1, 'gimsk': 1, 'gicr': 1, 'eifr': 1, 'gifr': 1, 'timsk': 1, 'tifr': 1, 'mcucr': 1, + 'mcucsr': 1, 'tccr0': 1, 'tcnt0': 1, 'ocr0': 1, 'assr': 1, 'tccr1a': 1, 'tccr1b': 1, 'tcnt1h': 1, 'tcnt1l': 1, + 'ocr1ah': 1, 'ocr1al': 1, 'ocr1bh': 1, 'ocr1bl': 1, 'icr1h': 1, 'icr1l': 1, 'tccr2': 1, 'tcnt2': 1, 'ocr2': 1, + 'ocdr': 1, 'wdtcr': 1, 'sfior': 1, 'eearh': 1, 'eearl': 1, 'eedr': 1, 'eecr': 1, 'porta': 1, 'ddra': 1, 'pina': 1, + 'portb': 1, 'ddrb': 1, 'pinb': 1, 'portc': 1, 'ddrc': 1, 'pinc': 1, 'portd': 1, 'ddrd': 1, 'pind': 1, 'spdr': 1, + 'spsr': 1, 'spcr': 1, 'udr0': 1, 'ucsr0a': 1, 'ucsr0b': 1, 'ubrr0l': 1, 'acsr': 1, 'admux': 1, 'adcsr': 1, 'adch': 1, + 'adcl': 1, 'porte': 1, 'ddre': 1, 'pine': 1, 'pinf': 1 + } + }, + contains: [ + hljs.C_BLOCK_COMMENT_MODE, + {className: 'comment', begin: ';', end: '$'}, + hljs.C_NUMBER_MODE, // 0x..., decimal, float + hljs.BINARY_NUMBER_MODE, // 0b... + { + className: 'number', + begin: '\\b(\\$[a-zA-Z0-9]+|0o[0-7]+)' // $..., 0o... + }, + hljs.QUOTE_STRING_MODE, + { + className: 'string', + begin: '\'', end: '[^\\\\]\'', + illegal: '[^\\\\][^\']' + }, + {className: 'label', begin: '^[A-Za-z0-9_.$]+:'}, + {className: 'preprocessor', begin: '#', end: '$'}, + { // директивы «.include» «.macro» и т.д. + className: 'preprocessor', + begin: '\\.[a-zA-Z]+' + }, + { // подстановка в «.macro» + className: 'localvars', + begin: '@[0-9]+' + } + ] + } +}; + diff --git a/Scratch/js/highlight/languages/axapta.js b/Scratch/js/highlight/languages/axapta.js new file mode 100644 index 0000000..6c49ea0 --- /dev/null +++ b/Scratch/js/highlight/languages/axapta.js @@ -0,0 +1,48 @@ +/* +Language: Axapta +Author: Dmitri Roudakov +*/ + +hljs.LANGUAGES.axapta = { + defaultMode: { + keywords: { + 'false': 1, 'int': 1, 'abstract': 1, 'private': 1, 'char': 1, 'interface': 1, 'boolean': 1, 'static': 1, + 'null': 1, 'if': 1, 'for': 1, 'true': 1, 'while': 1, 'long': 1, 'throw': 1, 'finally': 1, 'protected': 1, + 'extends': 1, 'final': 1, 'implements': 1, 'return': 1, 'void': 1, 'enum': 1, 'else': 1, 'break': 1, 'new': 1, + 'catch': 1, 'byte': 1, 'super': 1, 'class': 1, 'case': 1, 'short': 1, 'default': 1, 'double': 1, 'public': 1, + 'try': 1, 'this': 1, 'switch': 1, 'continue': 1, 'reverse': 1, 'firstfast': 1, 'firstonly': 1, 'forupdate': 1, + 'nofetch': 1, 'sum': 1, 'avg': 1, 'minof': 1, 'maxof': 1, 'count': 1, 'order': 1, 'group': 1, 'by': 1, 'asc': 1, + 'desc': 1, 'index': 1, 'hint': 1, 'like': 1, 'dispaly': 1, 'edit': 1, 'client': 1, 'server': 1, 'ttsbegin': 1, + 'ttscommit': 1, 'str': 1, 'real': 1, 'date': 1, 'container': 1, 'anytype': 1, 'common': 1, 'div': 1, 'mod': 1 + }, + contains: [ + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE, + hljs.C_NUMBER_MODE, + { + className: 'preprocessor', + begin: '#', end: '$' + }, + { + className: 'class', + begin: '(class |interface )', end: '{', + illegal: ':', + keywords: {'class': 1, 'interface': 1}, + contains: [ + { + className: 'inheritance', + begin: '(implements|extends)', + keywords: {'extends': 1, 'implements': 1}, + relevance: 10 + }, + { + className: 'title', + begin: hljs.UNDERSCORE_IDENT_RE + } + ] + } + ] + } +}; diff --git a/Scratch/js/highlight/languages/bash.js b/Scratch/js/highlight/languages/bash.js new file mode 100644 index 0000000..1814948 --- /dev/null +++ b/Scratch/js/highlight/languages/bash.js @@ -0,0 +1,65 @@ +/* +Language: Bash +Author: vah +*/ + +hljs.LANGUAGES.bash = function(){ + var BASH_LITERAL = {'true' : 1, 'false' : 1}; + var VAR1 = { + className: 'variable', + begin: '\\$([a-zA-Z0-9_]+)\\b' + }; + var VAR2 = { + className: 'variable', + begin: '\\$\\{(([^}])|(\\\\}))+\\}', + contains: [hljs.C_NUMBER_MODE] + }; + var QUOTE_STRING = { + className: 'string', + begin: '"', end: '"', + illegal: '\\n', + contains: [hljs.BACKSLASH_ESCAPE, VAR1, VAR2], + relevance: 0 + }; + var APOS_STRING = { + className: 'string', + begin: '\'', end: '\'', + relevance: 0 + }; + var TEST_CONDITION = { + className: 'test_condition', + begin: '', end: '', + contains: [QUOTE_STRING, APOS_STRING, VAR1, VAR2, hljs.C_NUMBER_MODE], + keywords: { + 'literal': BASH_LITERAL + }, + relevance: 0 + }; + + return { + defaultMode: { + keywords: { + 'keyword': { + 'if' : 1, 'then' : 1, 'else' : 1, 'fi' : 1, 'for' : 1, 'break' : 1, 'continue' : 1, 'while' : 1, 'in' : 1, + 'do' : 1, 'done' : 1, 'echo' : 1, 'exit' : 1, 'return' : 1, 'set' : 1, 'declare' : 1 + }, + 'literal': BASH_LITERAL + }, + contains: [ + { + className: 'shebang', + begin: '(#!\\/bin\\/bash)|(#!\\/bin\\/sh)', + relevance: 10 + }, + VAR1, + VAR2, + hljs.HASH_COMMENT_MODE, + hljs.C_NUMBER_MODE, + QUOTE_STRING, + APOS_STRING, + hljs.inherit(TEST_CONDITION, {begin: '\\[ ', end: ' \\]', relevance: 0}), + hljs.inherit(TEST_CONDITION, {begin: '\\[\\[ ', end: ' \\]\\]'}) + ] + } + }; +}(); diff --git a/Scratch/js/highlight/languages/cmake.js b/Scratch/js/highlight/languages/cmake.js new file mode 100644 index 0000000..8aa9337 --- /dev/null +++ b/Scratch/js/highlight/languages/cmake.js @@ -0,0 +1,42 @@ +/* +Language: CMake +Description: CMake is an open-source cross-platform system for build automation. +Author: Igor Kalnitsky +Website: http://kalnitsky.org.ua/ +*/ + +hljs.LANGUAGES.cmake = { + case_insensitive: true, + defaultMode: { + keywords: { + 'add_custom_command': 2, 'add_custom_target': 2, 'add_definitions': 2, 'add_dependencies': 2, + 'add_executable': 2, 'add_library': 2, 'add_subdirectory': 2, 'add_test': 2, 'aux_source_directory': 2, + 'break': 1, 'build_command': 2, 'cmake_minimum_required': 3, 'cmake_policy': 3, 'configure_file': 1, + 'create_test_sourcelist': 1, 'define_property': 1, 'else': 1, 'elseif': 1, 'enable_language': 2, + 'enable_testing': 2, 'endforeach': 1, 'endfunction': 1, 'endif': 1, 'endmacro': 1, 'endwhile': 1, + 'execute_process': 2, 'export': 1, 'find_file': 1, 'find_library': 2, 'find_package': 2, 'find_path': 1, + 'find_program': 1, 'fltk_wrap_ui': 2, 'foreach': 1, 'function': 1, 'get_cmake_property': 3, + 'get_directory_property': 1, 'get_filename_component': 1, 'get_property': 1, 'get_source_file_property': 1, + 'get_target_property': 1, 'get_test_property': 1, 'if': 1, 'include': 1, 'include_directories': 2, + 'include_external_msproject': 1, 'include_regular_expression': 2, 'install': 1, 'link_directories': 1, + 'load_cache': 1, 'load_command': 1, 'macro': 1, 'mark_as_advanced': 1, 'message': 1, 'option': 1, + 'output_required_files': 1, 'project': 1, 'qt_wrap_cpp': 2, 'qt_wrap_ui': 2, 'remove_definitions': 2, + 'return': 1, 'separate_arguments': 1, 'set': 1, 'set_directory_properties': 1, 'set_property': 1, + 'set_source_files_properties': 1, 'set_target_properties': 1, 'set_tests_properties': 1, 'site_name': 1, + 'source_group': 1, 'string': 1, 'target_link_libraries': 2, 'try_compile': 2, 'try_run': 2, 'unset': 1, + 'variable_watch': 2, 'while': 1, 'build_name': 1, 'exec_program': 1, 'export_library_dependencies': 1, + 'install_files': 1, 'install_programs': 1, 'install_targets': 1, 'link_libraries': 1, 'make_directory': 1, + 'remove': 1, 'subdir_depends': 1, 'subdirs': 1, 'use_mangled_mesa': 1, 'utility_source': 1, + 'variable_requires': 1, 'write_file': 1 + }, + contains: [ + { + className: 'envvar', + begin: '\\${', end: '}' + }, + hljs.HASH_COMMENT_MODE, + hljs.QUOTE_STRING_MODE, + hljs.NUMBER_MODE + ] + } +}; diff --git a/Scratch/js/highlight/languages/coffeescript.js b/Scratch/js/highlight/languages/coffeescript.js new file mode 100755 index 0000000..7cd5cec --- /dev/null +++ b/Scratch/js/highlight/languages/coffeescript.js @@ -0,0 +1,112 @@ +/* +Language: CoffeeScript +Author: Dmytrii Nagirniak +Contributors: Oleg Efimov +Description: CoffeeScript is a programming language that transcompiles to JavaScript. For info about language see http://coffeescript.org/ +*/ + +hljs.LANGUAGES.coffeescript = function() { + var keywords = { + 'keyword': { + // JS keywords + 'in': 1, 'if': 1, 'for': 1, 'while': 1, 'finally': 1, + 'new': 1, 'do': 1, 'return': 1, 'else': 1, + 'break': 1, 'catch': 1, 'instanceof': 1, 'throw': 1, + 'try': 1, 'this': 1, 'switch': 1, 'continue': 1, 'typeof': 1, + 'delete': 1, 'debugger': 1, + 'class': 1, 'extends': 1, 'super': 1, + // Coffee keywords + 'then': 1, 'unless': 1, 'until': 1, 'loop': 2, 'of': 2, 'by': 1, 'when': 2, + 'and': 1, 'or': 1, 'is': 1, 'isnt': 2, 'not': 1 + }, + 'literal': { + // JS literals + 'true': 1, 'false': 1, 'null': 1, 'undefined': 1, + // Coffee literals + 'yes': 1, 'no': 1, 'on': 1, 'off': 1 + }, + 'reserved': { + 'case': 1, 'default': 1, 'function': 1, 'var': 1, 'void': 1, 'with': 1, + 'const': 1, 'let': 1, 'enum': 1, 'export': 1, 'import': 1, 'native': 1, + '__hasProp': 1 , '__extends': 1 , '__slice': 1 , '__bind': 1 , '__indexOf': 1 + } + }; + + var JS_IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*'; + + var COFFEE_QUOTE_STRING_SUBST_MODE = { + className: 'subst', + begin: '#\\{', end: '}', + keywords: keywords, + contains: [hljs.C_NUMBER_MODE, hljs.BINARY_NUMBER_MODE] + }; + + var COFFEE_QUOTE_STRING_MODE = { + className: 'string', + begin: '"', end: '"', + relevance: 0, + contains: [hljs.BACKSLASH_ESCAPE, COFFEE_QUOTE_STRING_SUBST_MODE] + }; + + var COFFEE_HEREDOC_MODE = { + className: 'string', + begin: '"""', end: '"""', + contains: [hljs.BACKSLASH_ESCAPE, COFFEE_QUOTE_STRING_SUBST_MODE] + }; + + var COFFEE_HERECOMMENT_MODE = { + className: 'comment', + begin: '###', end: '###' + }; + + var COFFEE_HEREGEX_MODE = { + className: 'regexp', + begin: '///', end: '///', + contains: [hljs.HASH_COMMENT_MODE] + }; + + var COFFEE_FUNCTION_DECLARATION_MODE = { + className: 'function', + begin: JS_IDENT_RE + '\\s*=\\s*(\\(.+\\))?\\s*[-=]>', + returnBegin: true, + contains: [ + { + className: 'title', + begin: JS_IDENT_RE + }, + { + className: 'params', + begin: '\\(', end: '\\)' + } + ] + }; + + var COFFEE_EMBEDDED_JAVASCRIPT = { + className: 'javascript', + begin: '`', end: '`', + excludeBegin: true, excludeEnd: true, + subLanguage: 'javascript' + }; + + return { + defaultMode: { + keywords: keywords, + contains: [ + // Numbers + hljs.C_NUMBER_MODE, + hljs.BINARY_NUMBER_MODE, + // Strings + hljs.APOS_STRING_MODE, + COFFEE_HEREDOC_MODE, // Should be before COFFEE_QUOTE_STRING_MODE for greater priority + COFFEE_QUOTE_STRING_MODE, + // Comments + COFFEE_HERECOMMENT_MODE, // Should be before hljs.HASH_COMMENT_MODE for greater priority + hljs.HASH_COMMENT_MODE, + // CoffeeScript specific modes + COFFEE_HEREGEX_MODE, + COFFEE_EMBEDDED_JAVASCRIPT, + COFFEE_FUNCTION_DECLARATION_MODE + ] + } + }; +}(); diff --git a/Scratch/js/highlight/languages/cpp.js b/Scratch/js/highlight/languages/cpp.js new file mode 100644 index 0000000..f9872a7 --- /dev/null +++ b/Scratch/js/highlight/languages/cpp.js @@ -0,0 +1,66 @@ +/* +Language: C++ +Contributors: Evgeny Stepanischev +*/ + +hljs.LANGUAGES.cpp = function(){ + var CPP_KEYWORDS = { + 'keyword': { + 'false': 1, 'int': 1, 'float': 1, 'while': 1, 'private': 1, 'char': 1, + 'catch': 1, 'export': 1, 'virtual': 1, 'operator': 2, 'sizeof': 2, + 'dynamic_cast': 2, 'typedef': 2, 'const_cast': 2, 'const': 1, + 'struct': 1, 'for': 1, 'static_cast': 2, 'union': 1, 'namespace': 1, + 'unsigned': 1, 'long': 1, 'throw': 1, 'volatile': 2, 'static': 1, + 'protected': 1, 'bool': 1, 'template': 1, 'mutable': 1, 'if': 1, + 'public': 1, 'friend': 2, 'do': 1, 'return': 1, 'goto': 1, 'auto': 1, + 'void': 2, 'enum': 1, 'else': 1, 'break': 1, 'new': 1, 'extern': 1, + 'using': 1, 'true': 1, 'class': 1, 'asm': 1, 'case': 1, 'typeid': 1, + 'short': 1, 'reinterpret_cast': 2, 'default': 1, 'double': 1, + 'register': 1, 'explicit': 1, 'signed': 1, 'typename': 1, 'try': 1, + 'this': 1, 'switch': 1, 'continue': 1, 'wchar_t': 1, 'inline': 1, + 'delete': 1, 'alignof': 1, 'char16_t': 1, 'char32_t': 1, 'constexpr': 1, + 'decltype': 1, 'noexcept': 1, 'nullptr': 1, 'static_assert': 1, + 'thread_local': 1, 'restrict': 1, '_Bool':1, 'complex': 1 + }, + 'built_in': { + 'std': 1, 'string': 1, 'cin': 1, 'cout': 1, 'cerr': 1, 'clog': 1, + 'stringstream': 1, 'istringstream': 1, 'ostringstream': 1, 'auto_ptr': 1, + 'deque': 1, 'list': 1, 'queue': 1, 'stack': 1, 'vector': 1, 'map': 1, + 'set': 1, 'bitset': 1, 'multiset': 1, 'multimap': 1, 'unordered_set': 1, + 'unordered_map': 1, 'unordered_multiset': 1, 'unordered_multimap': 1, + 'array': 1, 'shared_ptr': 1 + } + }; + return { + defaultMode: { + keywords: CPP_KEYWORDS, + illegal: '', + keywords: CPP_KEYWORDS, + relevance: 10, + contains: ['self'] + } + ] + } + }; +}(); diff --git a/Scratch/js/highlight/languages/cs.js b/Scratch/js/highlight/languages/cs.js new file mode 100644 index 0000000..af43861 --- /dev/null +++ b/Scratch/js/highlight/languages/cs.js @@ -0,0 +1,58 @@ +/* +Language: C# +Author: Jason Diamond +*/ + +hljs.LANGUAGES.cs = { + defaultMode: { + keywords: { + // Normal keywords. + 'abstract': 1, 'as': 1, 'base': 1, 'bool': 1, 'break': 1, 'byte': 1, 'case': 1, 'catch': 1, 'char': 1, + 'checked': 1, 'class': 1, 'const': 1, 'continue': 1, 'decimal': 1, 'default': 1, 'delegate': 1, 'do': 1, + 'double': 1, 'else': 1, 'enum': 1, 'event': 1, 'explicit': 1, 'extern': 1, 'false': 1, 'finally': 1, 'fixed': 1, + 'float': 1, 'for': 1, 'foreach': 1, 'goto': 1, 'if': 1, 'implicit': 1, 'in': 1, 'int': 1, 'interface': 1, + 'internal': 1, 'is': 1, 'lock': 1, 'long': 1, 'namespace': 1, 'new': 1, 'null': 1, 'object': 1, 'operator': 1, + 'out': 1, 'override': 1, 'params': 1, 'private': 1, 'protected': 1, 'public': 1, 'readonly': 1, 'ref': 1, + 'return': 1, 'sbyte': 1, 'sealed': 1, 'short': 1, 'sizeof': 1, 'stackalloc': 1, 'static': 1, 'string': 1, + 'struct': 1, 'switch': 1, 'this': 1, 'throw': 1, 'true': 1, 'try': 1, 'typeof': 1, 'uint': 1, 'ulong': 1, + 'unchecked': 1, 'unsafe': 1, 'ushort': 1, 'using': 1, 'virtual': 1, 'volatile': 1, 'void': 1, 'while': 1, + // Contextual keywords. + 'ascending': 1, 'descending': 1, 'from': 1, 'get': 1, 'group': 1, 'into': 1, 'join': 1, 'let': 1, 'orderby': 1, + 'partial': 1, 'select': 1, 'set': 1, 'value': 1, 'var': 1, 'where': 1, 'yield': 1 + }, + contains: [ + { + className: 'comment', + begin: '///', end: '$', returnBegin: true, + contains: [ + { + className: 'xmlDocTag', + begin: '///|' + }, + { + className: 'xmlDocTag', + begin: '' + } + ] + }, + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + { + className: 'preprocessor', + begin: '#', end: '$', + keywords: { + 'if': 1, 'else': 1, 'elif': 1, 'endif': 1, 'define': 1, 'undef': 1, 'warning': 1, + 'error': 1, 'line': 1, 'region': 1, 'endregion': 1, 'pragma': 1, 'checksum': 1 + } + }, + { + className: 'string', + begin: '@"', end: '"', + contains: [{begin: '""'}] + }, + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE, + hljs.C_NUMBER_MODE + ] + } +}; diff --git a/Scratch/js/highlight/languages/css.js b/Scratch/js/highlight/languages/css.js new file mode 100644 index 0000000..b6e4db9 --- /dev/null +++ b/Scratch/js/highlight/languages/css.js @@ -0,0 +1,101 @@ +/* +Language: CSS +*/ + +hljs.LANGUAGES.css = function() { + var FUNCTION = { + className: 'function', + begin: hljs.IDENT_RE + '\\(', end: '\\)', + contains: [{ + endsWithParent: true, excludeEnd: true, + contains: [hljs.NUMBER_MODE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE] + }] + }; + return { + case_insensitive: true, + defaultMode: { + illegal: '[=/|\']', + contains: [ + hljs.C_BLOCK_COMMENT_MODE, + { + className: 'id', begin: '\\#[A-Za-z0-9_-]+' + }, + { + className: 'class', begin: '\\.[A-Za-z0-9_-]+', + relevance: 0 + }, + { + className: 'attr_selector', + begin: '\\[', end: '\\]', + illegal: '$' + }, + { + className: 'pseudo', + begin: ':(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\"\\\']+' + }, + { + className: 'at_rule', + begin: '@(font-face|page)', + lexems: '[a-z-]+', + keywords: {'font-face': 1, 'page': 1} + }, + { + className: 'at_rule', + begin: '@', end: '[{;]', // at_rule eating first "{" is a good thing + // because it doesn't let it to be parsed as + // a rule set but instead drops parser into + // the defaultMode which is how it should be. + excludeEnd: true, + keywords: {'import': 1, 'page': 1, 'media': 1, 'charset': 1}, + contains: [ + FUNCTION, + hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, + hljs.NUMBER_MODE + ] + }, + { + className: 'tag', begin: hljs.IDENT_RE, + relevance: 0 + }, + { + className: 'rules', + begin: '{', end: '}', + illegal: '[^\\s]', + relevance: 0, + contains: [ + hljs.C_BLOCK_COMMENT_MODE, + { + className: 'rule', + begin: '[^\\s]', returnBegin: true, end: ';', endsWithParent: true, + contains: [ + { + className: 'attribute', + begin: '[A-Z\\_\\.\\-]+', end: ':', + excludeEnd: true, + illegal: '[^\\s]', + starts: { + className: 'value', + endsWithParent: true, excludeEnd: true, + contains: [ + FUNCTION, + hljs.NUMBER_MODE, + hljs.QUOTE_STRING_MODE, + hljs.APOS_STRING_MODE, + hljs.C_BLOCK_COMMENT_MODE, + { + className: 'hexcolor', begin: '\\#[0-9A-F]+' + }, + { + className: 'important', begin: '!important' + } + ] + } + } + ] + } + ] + } + ] + } + }; +}(); diff --git a/Scratch/js/highlight/languages/delphi.js b/Scratch/js/highlight/languages/delphi.js new file mode 100644 index 0000000..eb50b8f --- /dev/null +++ b/Scratch/js/highlight/languages/delphi.js @@ -0,0 +1,88 @@ +/* +Language: Delphi +*/ + +hljs.LANGUAGES.delphi = function(){ + var DELPHI_KEYWORDS = { + 'and': 1, 'safecall': 1, 'cdecl': 1, 'then': 1, 'string': 1, 'exports': 1, 'library': 1, 'not': 1, 'pascal': 1, + 'set': 1, 'virtual': 1, 'file': 1, 'in': 1, 'array': 1, 'label': 1, 'packed': 1, 'end.': 1, 'index': 1, + 'while': 1, 'const': 1, 'raise': 1, 'for': 1, 'to': 1, 'implementation': 1, 'with': 1, 'except': 1, + 'overload': 1, 'destructor': 1, 'downto': 1, 'finally': 1, 'program': 1, 'exit': 1, 'unit': 1, 'inherited': 1, + 'override': 1, 'if': 1, 'type': 1, 'until': 1, 'function': 1, 'do': 1, 'begin': 1, 'repeat': 1, 'goto': 1, + 'nil': 1, 'far': 1, 'initialization': 1, 'object': 1, 'else': 1, 'var': 1, 'uses': 1, 'external': 1, + 'resourcestring': 1, 'interface': 1, 'end': 1, 'finalization': 1, 'class': 1, 'asm': 1, 'mod': 1, 'case': 1, + 'on': 1, 'shr': 1, 'shl': 1, 'of': 1, 'register': 1, 'xorwrite': 1, 'threadvar': 1, 'try': 1, 'record': 1, + 'near': 1, 'stored': 1, 'constructor': 1, 'stdcall': 1, 'inline': 1, 'div': 1, 'out': 1, 'or': 1, 'procedure': 1 + }; + var DELPHI_CLASS_KEYWORDS = { + 'safecall': 1, 'stdcall': 1, 'pascal': 1, 'stored': 1, 'const': 1, 'implementation': 1, 'finalization': 1, + 'except': 1, 'to': 1, 'finally': 1, 'program': 1, 'inherited': 1, 'override': 1, 'then': 1, 'exports': 1, + 'string': 1, 'read': 1, 'not': 1, 'mod': 1, 'shr': 1, 'try': 1, 'div': 1, 'shl': 1, 'set': 1, 'library': 1, + 'message': 1, 'packed': 1, 'index': 1, 'for': 1, 'near': 1, 'overload': 1, 'label': 1, 'downto': 1, 'exit': 1, + 'public': 1, 'goto': 1, 'interface': 1, 'asm': 1, 'on': 1, 'of': 1, 'constructor': 1, 'or': 1, 'private': 1, + 'array': 1, 'unit': 1, 'raise': 1, 'destructor': 1, 'var': 1, 'type': 1, 'until': 1, 'function': 1, 'else': 1, + 'external': 1, 'with': 1, 'case': 1, 'default': 1, 'record': 1, 'while': 1, 'protected': 1, 'property': 1, + 'procedure': 1, 'published': 1, 'and': 1, 'cdecl': 1, 'do': 1, 'threadvar': 1, 'file': 1, 'in': 1, 'if': 1, + 'end': 1, 'virtual': 1, 'write': 1, 'far': 1, 'out': 1, 'begin': 1, 'repeat': 1, 'nil': 1, 'initialization': 1, + 'object': 1, 'uses': 1, 'resourcestring': 1, 'class': 1, 'register': 1, 'xorwrite': 1, 'inline': 1, 'static': 1 + }; + var CURLY_COMMENT = { + className: 'comment', + begin: '{', end: '}', + relevance: 0 + }; + var PAREN_COMMENT = { + className: 'comment', + begin: '\\(\\*', end: '\\*\\)', + relevance: 10 + }; + var STRING = { + className: 'string', + begin: '\'', end: '\'', + contains: [{begin: '\'\''}], + relevance: 0 + }; + var CHAR_STRING = { + className: 'string', begin: '(#\\d+)+' + }; + var FUNCTION = { + className: 'function', + begin: '(procedure|constructor|destructor|function)\\b', end: '[:;]', + keywords: {'function': 1, 'constructor': 10, 'destructor': 10, 'procedure': 10}, + contains: [ + { + className: 'title', begin: hljs.IDENT_RE + }, + { + className: 'params', + begin: '\\(', end: '\\)', + keywords: DELPHI_KEYWORDS, + contains: [STRING, CHAR_STRING] + }, + CURLY_COMMENT, PAREN_COMMENT + ] + }; + return { + case_insensitive: true, + defaultMode: { + keywords: DELPHI_KEYWORDS, + illegal: '("|\\$[G-Zg-z]|\\/\\*| +*/ + +hljs.LANGUAGES.diff = { + case_insensitive: true, + defaultMode: { + contains: [ + { + className: 'chunk', + begin: '^\\@\\@ +\\-\\d+,\\d+ +\\+\\d+,\\d+ +\\@\\@$', + relevance: 10 + }, + { + className: 'chunk', + begin: '^\\*\\*\\* +\\d+,\\d+ +\\*\\*\\*\\*$', + relevance: 10 + }, + { + className: 'chunk', + begin: '^\\-\\-\\- +\\d+,\\d+ +\\-\\-\\-\\-$', + relevance: 10 + }, + { + className: 'header', + begin: 'Index: ', end: '$' + }, + { + className: 'header', + begin: '=====', end: '=====$' + }, + { + className: 'header', + begin: '^\\-\\-\\-', end: '$' + }, + { + className: 'header', + begin: '^\\*{3} ', end: '$' + }, + { + className: 'header', + begin: '^\\+\\+\\+', end: '$' + }, + { + className: 'header', + begin: '\\*{5}', end: '\\*{5}$' + }, + { + className: 'addition', + begin: '^\\+', end: '$' + }, + { + className: 'deletion', + begin: '^\\-', end: '$' + }, + { + className: 'change', + begin: '^\\!', end: '$' + } + ] + } +}; diff --git a/Scratch/js/highlight/languages/django.js b/Scratch/js/highlight/languages/django.js new file mode 100644 index 0000000..f5037c1 --- /dev/null +++ b/Scratch/js/highlight/languages/django.js @@ -0,0 +1,81 @@ +/* +Language: Django +Requires: xml.js +*/ + +hljs.LANGUAGES.django = function() { + + function allowsDjangoSyntax(mode, parent) { + return ( + parent == undefined || // defaultMode + (!mode.className && parent.className == 'tag') || // tag_internal + mode.className == 'value' // value + ); + } + + function copy(mode, parent) { + var result = {}; + for (var key in mode) { + if (key != 'contains') { + result[key] = mode[key]; + } + var contains = []; + for (var i = 0; mode.contains && i < mode.contains.length; i++) { + contains.push(copy(mode.contains[i], mode)); + } + if (allowsDjangoSyntax(mode, parent)) { + contains = DJANGO_CONTAINS.concat(contains); + } + if (contains.length) { + result.contains = contains; + } + } + return result; + } + + var FILTER = { + className: 'filter', + begin: '\\|[A-Za-z]+\\:?', excludeEnd: true, + keywords: { + 'truncatewords': 1, 'removetags': 1, 'linebreaksbr': 1, 'yesno': 1, 'get_digit': 1, 'timesince': 1, 'random': 1, + 'striptags': 1, 'filesizeformat': 1, 'escape': 1, 'linebreaks': 1, 'length_is': 1, 'ljust': 1, 'rjust': 1, + 'cut': 1, 'urlize': 1, 'fix_ampersands': 1, 'title': 1, 'floatformat': 1, 'capfirst': 1, 'pprint': 1, + 'divisibleby': 1, 'add': 1, 'make_list': 1, 'unordered_list': 1, 'urlencode': 1, 'timeuntil': 1, + 'urlizetrunc': 1, 'wordcount': 1, 'stringformat': 1, 'linenumbers': 1, 'slice': 1, 'date': 1, 'dictsort': 1, + 'dictsortreversed': 1, 'default_if_none': 1, 'pluralize': 1, 'lower': 1, 'join': 1, 'center': 1, 'default': 1, + 'truncatewords_html': 1, 'upper': 1, 'length': 1, 'phone2numeric': 1, 'wordwrap': 1, 'time': 1, 'addslashes': 1, + 'slugify': 1, 'first': 1 + }, + contains: [ + {className: 'argument', begin: '"', end: '"'} + ] + }; + + var DJANGO_CONTAINS = [ + { + className: 'template_comment', + begin: '{%\\s*comment\\s*%}', end: '{%\\s*endcomment\\s*%}' + }, + { + className: 'template_comment', + begin: '{#', end: '#}' + }, + { + className: 'template_tag', + begin: '{%', end: '%}', + keywords: {'comment': 1, 'endcomment': 1, 'load': 1, 'templatetag': 1, 'ifchanged': 1, 'endifchanged': 1, 'if': 1, 'endif': 1, 'firstof': 1, 'for': 1, 'endfor': 1, 'in': 1, 'ifnotequal': 1, 'endifnotequal': 1, 'widthratio': 1, 'extends': 1, 'include': 1, 'spaceless': 1, 'endspaceless': 1, 'regroup': 1, 'by': 1, 'as': 1, 'ifequal': 1, 'endifequal': 1, 'ssi': 1, 'now': 1, 'with': 1, 'cycle': 1, 'url': 1, 'filter': 1, 'endfilter': 1, 'debug': 1, 'block': 1, 'endblock': 1, 'else': 1}, + contains: [FILTER] + }, + { + className: 'variable', + begin: '{{', end: '}}', + contains: [FILTER] + } + ]; + + return { + case_insensitive: true, + defaultMode: copy(hljs.LANGUAGES.xml.defaultMode) + }; + +}(); diff --git a/Scratch/js/highlight/languages/dos.js b/Scratch/js/highlight/languages/dos.js new file mode 100644 index 0000000..a2fee61 --- /dev/null +++ b/Scratch/js/highlight/languages/dos.js @@ -0,0 +1,35 @@ +/* +Language: DOS .bat +Author: Alexander Makarov (http://rmcreative.ru/) +*/ + +hljs.LANGUAGES.dos = { + case_insensitive: true, + defaultMode: { + keywords: { + 'flow': {'if':1, 'else':1, 'goto':1, 'for':1, 'in':1, 'do':1, 'call':1, 'exit':1, 'not':1, 'exist':1, 'errorlevel':1, 'defined':1, 'equ':1, 'neq':1, 'lss':1, 'leq':1, 'gtr':1, 'geq':1}, + 'keyword':{'shift':1, 'cd':1, 'dir':1, 'echo':1, 'setlocal':1, 'endlocal':1, 'set':1, 'pause':1, 'copy':1}, + 'stream':{'prn':1, 'nul':1, 'lpt3':1, 'lpt2':1, 'lpt1':1, 'con':1, 'com4':1, 'com3':1, 'com2':1, 'com1':1, 'aux':1}, + 'winutils':{'ping':1, 'net':1, 'ipconfig':1, 'taskkill':1, 'xcopy':1, 'ren':1, 'del':1} + }, + contains: [ + { + className: 'envvar', begin: '%%[^ ]' + }, + { + className: 'envvar', begin: '%[^ ]+?%' + }, + { + className: 'envvar', begin: '![^ ]+?!' + }, + { + className: 'number', begin: '\\b\\d+', + relevance: 0 + }, + { + className: 'comment', + begin: '@?rem', end: '$' + } + ] + } +}; diff --git a/Scratch/js/highlight/languages/erlang-repl.js b/Scratch/js/highlight/languages/erlang-repl.js new file mode 100644 index 0000000..c552476 --- /dev/null +++ b/Scratch/js/highlight/languages/erlang-repl.js @@ -0,0 +1,85 @@ +/* + Language: Erlang REPL + Author: Sergey Ignatov + */ + +hljs.LANGUAGES.erlang_repl = { + defaultMode: { + keywords: { + 'special_functions':{ + 'spawn':10, + 'spawn_link':10, + 'self':2 + }, + 'reserved':{ + 'after':1, + 'and':1, + 'andalso':5, + 'band':1, + 'begin':1, + 'bnot':1, + 'bor':1, + 'bsl':1, + 'bsr':1, + 'bxor':1, + 'case':1, + 'catch':0, + 'cond':1, + 'div':1, + 'end':1, + 'fun':0, + 'if':0, + 'let':1, + 'not':0, + 'of':1, + 'or':1, + 'orelse':5, + 'query':1, + 'receive':0, + 'rem':1, + 'try':0, + 'when':1, + 'xor':1 + } + }, + contains: [ + { + className: 'input_number', begin: '^[0-9]+> ', + relevance: 10 + }, + { + className: 'comment', + begin: '%', end: '$' + }, + { + className: 'number', + begin: '\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)', + relevance: 0 + }, + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE, + { + className: 'constant', begin: '\\?(::)?([A-Z]\\w*(::)?)+' + }, + { + className: 'arrow', begin: '->' + }, + { + className: 'ok', begin: 'ok' + }, + { + className: 'exclamation_mark', begin: '!' + }, + { + className: 'function_or_atom', + begin: '(\\b[a-z\'][a-zA-Z0-9_\']*:[a-z\'][a-zA-Z0-9_\']*)|(\\b[a-z\'][a-zA-Z0-9_\']*)', + relevance: 0 + }, + { + className: 'variable', + begin: '[A-Z][a-zA-Z0-9_\']*', + relevance: 0 + } + ] + } +}; diff --git a/Scratch/js/highlight/languages/erlang.js b/Scratch/js/highlight/languages/erlang.js new file mode 100644 index 0000000..cae906a --- /dev/null +++ b/Scratch/js/highlight/languages/erlang.js @@ -0,0 +1,206 @@ +/* +Language: Erlang +Description: Erlang is a general-purpose functional language, with strict evaluation, single assignment, and dynamic typing. +Author: Nikolay Zakharov , Dmitry Kovega +*/ + +hljs.LANGUAGES.erlang = function(){ + var BASIC_ATOM_RE = '[a-z\'][a-zA-Z0-9_\']*'; + var FUNCTION_NAME_RE = '(' + BASIC_ATOM_RE + ':' + BASIC_ATOM_RE + '|' + BASIC_ATOM_RE + ')'; + var ERLANG_RESERVED = { + 'keyword': { + 'after': 1, + 'and': 1, + 'andalso': 10, + 'band': 1, + 'begin': 1, + 'bnot': 1, + 'bor': 1, + 'bsl': 1, + 'bzr': 1, + 'bxor': 1, + 'case': 1, + 'catch': 1, + 'cond': 1, + 'div': 1, + 'end': 1, + 'fun': 1, + 'let': 1, + 'not': 1, + 'of': 1, + 'orelse': 10, + 'query': 1, + 'receive': 1, + 'rem': 1, + 'try': 1, + 'when': 1, + 'xor': 1 + }, + 'literal': {'false': 1, 'true': 1} + }; + + var COMMENT = { + className: 'comment', + begin: '%', end: '$', + relevance: 0 + }; + var NUMBER = { + className: 'number', + begin: '\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)', + relevance: 0 + }; + var NAMED_FUN = { + begin: 'fun\\s+' + BASIC_ATOM_RE + '/\\d+' + }; + var FUNCTION_CALL = { + begin: FUNCTION_NAME_RE + '\\(', end: '\\)', + returnBegin: true, + relevance: 0, + contains: [ + { + className: 'function_name', begin: FUNCTION_NAME_RE, + relevance: 0 + }, + { + begin: '\\(', end: '\\)', endsWithParent: true, + returnEnd: true, + relevance: 0 + // "contains" defined later + } + ] + }; + var TUPLE = { + className: 'tuple', + begin: '{', end: '}', + relevance: 0 + // "contains" defined later + }; + var VAR1 = { + className: 'variable', + begin: '\\b_([A-Z][A-Za-z0-9_]*)?', + relevance: 0 + }; + var VAR2 = { + className: 'variable', + begin: '[A-Z][a-zA-Z0-9_]*', + relevance: 0 + }; + var RECORD_ACCESS = { + begin: '#', end: '}', + illegal: '.', + relevance: 0, + returnBegin: true, + contains: [ + { + className: 'record_name', + begin: '#' + hljs.UNDERSCORE_IDENT_RE, + relevance: 0 + }, + { + begin: '{', endsWithParent: true, + relevance: 0 + // "contains" defined later + } + ] + }; + + var BLOCK_STATEMENTS = { + keywords: ERLANG_RESERVED, + begin: '(fun|receive|if|try|case)', end: 'end' + }; + BLOCK_STATEMENTS.contains = [ + COMMENT, + NAMED_FUN, + hljs.inherit(hljs.APOS_STRING_MODE, {className: ''}), + BLOCK_STATEMENTS, + FUNCTION_CALL, + hljs.QUOTE_STRING_MODE, + NUMBER, + TUPLE, + VAR1, VAR2, + RECORD_ACCESS + ]; + + var BASIC_MODES = [ + COMMENT, + NAMED_FUN, + BLOCK_STATEMENTS, + FUNCTION_CALL, + hljs.QUOTE_STRING_MODE, + NUMBER, + TUPLE, + VAR1, VAR2, + RECORD_ACCESS + ]; + FUNCTION_CALL.contains[1].contains = BASIC_MODES; + TUPLE.contains = BASIC_MODES; + RECORD_ACCESS.contains[1].contains = BASIC_MODES; + + var PARAMS = { + className: 'params', + begin: '\\(', end: '\\)', + endsWithParent: true, + contains: BASIC_MODES + }; + return { + defaultMode: { + keywords: ERLANG_RESERVED, + illegal: '(', endsWithParent: true, + contains: BASIC_MODES + } + ] + }, + COMMENT, + { + className: 'pp', + begin: '^-', end: '\\.', + relevance: 0, + excludeEnd: true, + returnBegin: true, + lexems: '-' + hljs.IDENT_RE, + keywords: { + '-module':1, + '-record':1, + '-undef':1, + '-export':1, + '-ifdef':1, + '-ifndef':1, + '-author':1, + '-copyright':1, + '-doc':1, + '-vsn':1, + '-import': 1, + '-include': 1, + '-include_lib': 1, + '-compile': 1, + '-define': 1, + '-else': 1, + '-endif': 1, + '-file': 1, + '-behaviour': 1, + '-behavior': 1 + }, + contains: [PARAMS] + }, + NUMBER, + hljs.QUOTE_STRING_MODE, + RECORD_ACCESS, + VAR1, VAR2, + TUPLE + ] + } + }; +}(); diff --git a/Scratch/js/highlight/languages/go.js b/Scratch/js/highlight/languages/go.js new file mode 100644 index 0000000..daf620f --- /dev/null +++ b/Scratch/js/highlight/languages/go.js @@ -0,0 +1,59 @@ +/* +Language: Go +Author: Stephan Kountso aka StepLg +Contributors: Evgeny Stepanischev +Description: Google go language (golang). For info about language see http://golang.org/ +*/ + +hljs.LANGUAGES.go = function(){ + var GO_KEYWORDS = { + 'keyword': { + 'break' : 1, 'default' : 1, 'func' : 1, 'interface' : 1, 'select' : 1, + 'case' : 1, 'map' : 1, 'struct' : 1, 'chan' : 1, + 'else' : 1, 'goto' : 1, 'package' : 1, 'switch' : 1, 'const' : 1, + 'fallthrough' : 1, 'if' : 1, 'range' : 1, 'type' : 1, 'continue' : 1, + 'for' : 1, 'import' : 1, 'return' : 1, 'var' : 1, 'go': 1, 'defer' : 1 + }, + 'constant': { + 'true': 1, 'false': 1, 'iota': 1, 'nil': 1 + }, + 'typename': { + 'bool': 1, 'byte': 1, 'complex64': 1, 'complex128': 1, 'float32': 1, + 'float64': 1, 'int8': 1, 'int16': 1, 'int32': 1, 'int64': 1, 'string': 1, + 'uint8': 1, 'uint16': 1, 'uint32': 1, 'uint64': 1, 'int': 1, 'uint': 1, + 'uintptr': 1, 'rune': 1 + }, + 'built_in': { + 'append': 1, 'cap': 1, 'close': 1, 'complex': 1, 'copy': 1, 'imag': 1, + 'len': 1, 'make': 1, 'new': 1, 'panic': 1, 'print': 1, 'println': 1, + 'real': 1, 'recover': 1, 'delete': 1 + } + }; + return { + defaultMode: { + keywords: GO_KEYWORDS, + illegal: ' +*/ + +hljs.LANGUAGES.haskell = function(){ + var LABEL = { + className: 'label', + begin: '\\b[A-Z][\\w\']*', + relevance: 0 + }; + var CONTAINER = { + className: 'container', + begin: '\\(', end: '\\)', + contains: [ + {className: 'label', begin: '\\b[A-Z][\\w\\(\\)\\.\']*'}, + {className: 'title', begin: '[_a-z][\\w\']*'} + ] + }; + + return { + defaultMode: { + keywords: { + 'keyword': { + 'let': 1, 'in': 1, 'if': 1, 'then': 1, 'else': 1, 'case': 1, 'of': 1, + 'where': 1, 'do': 1, 'module': 1, 'import': 1, 'hiding': 1, + 'qualified': 1, 'type': 1, 'data': 1, 'newtype': 1, 'deriving': 1, + 'class': 1, 'instance': 1, 'null': 1, 'not': 1, 'as': 1 + } + }, + contains: [ + { + className: 'comment', + begin: '--', end: '$' + }, + { + className: 'comment', + begin: '{-', end: '-}' + }, + { + className: 'string', + begin: '\\s+\'', end: '\'', + contains: [hljs.BACKSLASH_ESCAPE], + relevance: 0 + }, + hljs.QUOTE_STRING_MODE, + { + className: 'import', + begin: '\\bimport', end: '$', + keywords: {'import': 1, 'qualified': 1, 'as': 1, 'hiding': 1}, + contains: [CONTAINER] + }, + { + className: 'module', + begin: '\\bmodule', end: 'where', + keywords: {'module': 1, 'where': 1}, + contains: [CONTAINER] + }, + { + className: 'class', + begin: '\\b(class|instance|data|(new)?type)', end: '(where|$)', + keywords: {'class': 1, 'where': 1, 'instance': 1,'data': 1,'type': 1,'newtype': 1, 'deriving': 1}, + contains: [LABEL] + }, + hljs.C_NUMBER_MODE, + { + className: 'shebang', + begin: '#!\\/usr\\/bin\\/env\ runhaskell', end: '$' + }, + LABEL, + { + className: 'title', begin: '^[_a-z][\\w\']*' + } + ] + } + }; +}(); diff --git a/Scratch/js/highlight/languages/ini.js b/Scratch/js/highlight/languages/ini.js new file mode 100644 index 0000000..b68251f --- /dev/null +++ b/Scratch/js/highlight/languages/ini.js @@ -0,0 +1,32 @@ +/* +Language: Ini +*/ + +hljs.LANGUAGES.ini = { + case_insensitive: true, + defaultMode: { + illegal: '[^\\s]', + contains: [ + { + className: 'comment', + begin: ';', end: '$' + }, + { + className: 'title', + begin: '^\\[', end: '\\]' + }, + { + className: 'setting', + begin: '^[a-z0-9_\\[\\]]+[ \\t]*=[ \\t]*', end: '$', + contains: [ + { + className: 'value', + endsWithParent: true, + keywords: {'on': 1, 'off': 1, 'true': 1, 'false': 1, 'yes': 1, 'no': 1}, + contains: [hljs.QUOTE_STRING_MODE, hljs.NUMBER_MODE] + } + ] + } + ] + } +}; diff --git a/Scratch/js/highlight/languages/java.js b/Scratch/js/highlight/languages/java.js new file mode 100644 index 0000000..0bc745e --- /dev/null +++ b/Scratch/js/highlight/languages/java.js @@ -0,0 +1,53 @@ +/* +Language: Java +Author: Vsevolod Solovyov +*/ + +hljs.LANGUAGES.java = { + defaultMode: { + keywords: { + 'false': 1, 'synchronized': 1, 'int': 1, 'abstract': 1, 'float': 1, 'private': 1, 'char': 1, 'interface': 1, + 'boolean': 1, 'static': 1, 'null': 1, 'if': 1, 'const': 1, 'for': 1, 'true': 1, 'while': 1, 'long': 1, + 'throw': 1, 'strictfp': 1, 'finally': 1, 'protected': 1, 'extends': 1, 'import': 1, 'native': 1, 'final': 1, + 'implements': 1, 'return': 1, 'void': 1, 'enum': 1, 'else': 1, 'break': 1, 'transient': 1, 'new': 1, 'catch': 1, + 'instanceof': 1, 'byte': 1, 'super': 1, 'class': 1, 'volatile': 1, 'case': 1, 'assert': 1, 'short': 1, + 'package': 1, 'default': 1, 'double': 1, 'public': 1, 'try': 1, 'this': 1, 'switch': 1, 'continue': 1, + 'throws': 1 + }, + contains: [ + { + className: 'javadoc', + begin: '/\\*\\*', end: '\\*/', + contains: [{ + className: 'javadoctag', begin: '@[A-Za-z]+' + }], + relevance: 10 + }, + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE, + { + className: 'class', + begin: '(class |interface )', end: '{', + keywords: {'class': 1, 'interface': 1}, + illegal: ':', + contains: [ + { + begin: '(implements|extends)', + keywords: {'extends': 1, 'implements': 1}, + relevance: 10 + }, + { + className: 'title', + begin: hljs.UNDERSCORE_IDENT_RE + } + ] + }, + hljs.C_NUMBER_MODE, + { + className: 'annotation', begin: '@[A-Za-z]+' + } + ] + } +}; diff --git a/Scratch/js/highlight/languages/javascript.js b/Scratch/js/highlight/languages/javascript.js new file mode 100644 index 0000000..4f63cd5 --- /dev/null +++ b/Scratch/js/highlight/languages/javascript.js @@ -0,0 +1,57 @@ +/* +Language: JavaScript +*/ + +hljs.LANGUAGES.javascript = { + defaultMode: { + keywords: { + 'keyword': { + 'in': 1, 'if': 1, 'for': 1, 'while': 1, 'finally': 1, 'var': 1, 'new': 1, 'function': 1, 'do': 1, + 'return': 1, 'void': 1, 'else': 1, 'break': 1, 'catch': 1, 'instanceof': 1, 'with': 1, 'throw': 1, + 'case': 1, 'default': 1, 'try': 1, 'this': 1, 'switch': 1, 'continue': 1, 'typeof': 1, 'delete': 1 + }, + 'literal': {'true': 1, 'false': 1, 'null': 1} + }, + contains: [ + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE, + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + hljs.C_NUMBER_MODE, + { // regexp container + begin: '(' + hljs.RE_STARTERS_RE + '|case|return|throw)\\s*', + keywords: {'return': 1, 'throw': 1, 'case': 1}, + contains: [ + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + { + className: 'regexp', + begin: '/', end: '/[gim]*', + contains: [{begin: '\\\\/'}] + } + ], + relevance: 0 + }, + { + className: 'function', + begin: '\\bfunction\\b', end: '{', + keywords: {'function': 1}, + contains: [ + { + className: 'title', begin: '[A-Za-z$_][0-9A-Za-z$_]*' + }, + { + className: 'params', + begin: '\\(', end: '\\)', + contains: [ + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE, + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE + ] + } + ] + } + ] + } +}; diff --git a/Scratch/js/highlight/languages/lisp.js b/Scratch/js/highlight/languages/lisp.js new file mode 100644 index 0000000..e49e475 --- /dev/null +++ b/Scratch/js/highlight/languages/lisp.js @@ -0,0 +1,88 @@ +/* +Language: Lisp +Description: Generic lisp syntax +Author: Vasily Polovnyov +*/ + +hljs.LANGUAGES.lisp = function(){ + var LISP_IDENT_RE = '[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#]*'; + var LISP_SIMPLE_NUMBER_RE = '(\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s)(\\+|\\-)?\\d+)?'; + var LITERAL = { + className: 'literal', + begin: '\\b(t{1}|nil)\\b' + }; + var NUMBERS = [ + { + className: 'number', begin: LISP_SIMPLE_NUMBER_RE + }, + { + className: 'number', begin: '#b[0-1]+(/[0-1]+)?' + }, + { + className: 'number', begin: '#o[0-7]+(/[0-7]+)?' + }, + { + className: 'number', begin: '#x[0-9a-f]+(/[0-9a-f]+)?' + }, + { + className: 'number', begin: '#c\\(' + LISP_SIMPLE_NUMBER_RE + ' +' + LISP_SIMPLE_NUMBER_RE, end: '\\)' + } + ] + var STRING = { + className: 'string', + begin: '"', end: '"', + contains: [hljs.BACKSLASH_ESCAPE], + relevance: 0 + }; + var COMMENT = { + className: 'comment', + begin: ';', end: '$' + }; + var VARIABLE = { + className: 'variable', + begin: '\\*', end: '\\*' + }; + var KEYWORD = { + className: 'keyword', + begin: '[:&]' + LISP_IDENT_RE + }; + var QUOTED_LIST = { + begin: '\\(', end: '\\)', + contains: ['self', LITERAL, STRING].concat(NUMBERS) + }; + var QUOTED1 = { + className: 'quoted', + begin: '[\'`]\\(', end: '\\)', + contains: NUMBERS.concat([STRING, VARIABLE, KEYWORD, QUOTED_LIST]) + }; + var QUOTED2 = { + className: 'quoted', + begin: '\\(quote ', end: '\\)', + keywords: {'title': {'quote': 1}}, + contains: NUMBERS.concat([STRING, VARIABLE, KEYWORD, QUOTED_LIST]) + }; + var LIST = { + className: 'list', + begin: '\\(', end: '\\)' + }; + var BODY = { + className: 'body', + endsWithParent: true, excludeEnd: true + }; + LIST.contains = [{className: 'title', begin: LISP_IDENT_RE}, BODY]; + BODY.contains = [QUOTED1, QUOTED2, LIST, LITERAL].concat(NUMBERS).concat([STRING, COMMENT, VARIABLE, KEYWORD]); + + return { + case_insensitive: true, + defaultMode: { + illegal: '[^\\s]', + contains: NUMBERS.concat([ + LITERAL, + STRING, + COMMENT, + QUOTED1, QUOTED2, + LIST + ]) + } + }; +}(); diff --git a/Scratch/js/highlight/languages/lua.js b/Scratch/js/highlight/languages/lua.js new file mode 100644 index 0000000..827504f --- /dev/null +++ b/Scratch/js/highlight/languages/lua.js @@ -0,0 +1,75 @@ +/* +Language: Lua +Author: Andrew Fedorov +*/ + +hljs.LANGUAGES.lua = function() { + var OPENING_LONG_BRACKET = '\\[=*\\['; + var CLOSING_LONG_BRACKET = '\\]=*\\]'; + var LONG_BRACKETS = { + begin: OPENING_LONG_BRACKET, end: CLOSING_LONG_BRACKET, + contains: ['self'] + }; + var COMMENTS = [ + { + className: 'comment', + begin: '--(?!' + OPENING_LONG_BRACKET + ')', end: '$' + }, + { + className: 'comment', + begin: '--' + OPENING_LONG_BRACKET, end: CLOSING_LONG_BRACKET, + contains: [LONG_BRACKETS], + relevance: 10 + } + ] + return { + defaultMode: { + lexems: hljs.UNDERSCORE_IDENT_RE, + keywords: { + 'keyword': { + 'and': 1, 'break': 1, 'do': 1, 'else': 1, 'elseif': 1, 'end': 1, + 'false': 1, 'for': 1, 'if': 1, 'in': 1, 'local': 1, 'nil': 1, + 'not': 1, 'or': 1, 'repeat': 1, 'return': 1, 'then': 1, 'true': 1, + 'until': 1, 'while': 1 + }, + 'built_in': { + '_G': 1, '_VERSION': 1, 'assert': 1, 'collectgarbage': 1, 'dofile': 1, + 'error': 1, 'getfenv': 1, 'getmetatable': 1, 'ipairs': 1, 'load': 1, + 'loadfile': 1, 'loadstring': 1, 'module': 1, 'next': 1, 'pairs': 1, + 'pcall': 1, 'print': 1, 'rawequal': 1, 'rawget': 1, 'rawset': 1, + 'require': 1, 'select': 1, 'setfenv': 1, 'setmetatable': 1, + 'tonumber': 1, 'tostring': 1, 'type': 1, 'unpack': 1, 'xpcall': 1, + 'coroutine': 1, 'debug': 1, 'io': 1, 'math': 1, 'os': 1, 'package': 1, + 'string': 1, 'table': 1 + } + }, + contains: COMMENTS.concat([ + { + className: 'function', + begin: '\\bfunction\\b', end: '\\)', + keywords: {'function': 1}, + contains: [ + { + className: 'title', + begin: '([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*' + }, + { + className: 'params', + begin: '\\(', endsWithParent: true, + contains: COMMENTS + } + ].concat(COMMENTS) + }, + hljs.C_NUMBER_MODE, + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE, + { + className: 'string', + begin: OPENING_LONG_BRACKET, end: CLOSING_LONG_BRACKET, + contains: [LONG_BRACKETS], + relevance: 10 + } + ]) + } + }; +}(); diff --git a/Scratch/js/highlight/languages/markdown.js b/Scratch/js/highlight/languages/markdown.js new file mode 100644 index 0000000..06b3181 --- /dev/null +++ b/Scratch/js/highlight/languages/markdown.js @@ -0,0 +1,79 @@ +/* +Language: Markdown +Requires: xml.js +Author: John Crepezzi +Website: http://seejohncode.com/ +*/ + +hljs.LANGUAGES.markdown = { + case_insensitive: true, + defaultMode: { + contains: [ + // highlight headers + { + className: 'header', + begin: '^#{1,3}', end: '$' + }, + { + className: 'header', + begin: '^.+?\\n[=-]{2,}$' + }, + // inline html + { + begin: '<', end: '>', + subLanguage: 'xml' + }, + // lists (indicators only) + { + className: 'bullet', + begin: '^([*+-]|(\\d+\\.))\\s+' + }, + // strong segments + { + className: 'strong', + begin: '[*_]{2}.+?[*_]{2}' + }, + // emphasis segments + { + className: 'emphasis', + begin: '[*_].+?[*_]' + }, + // blockquotes + { + className: 'blockquote', + begin: '^>\\s+', end: '$' + }, + // code snippets + { + className: 'code', + begin: '`.+?`' + }, + { + className: 'code', + begin: '^ ', end: '$', + relevance: 0 + }, + // horizontal rules + { + className: 'horizontal_rule', + begin: '^-{3,}', end: '$' + }, + // using links - title and link + { + begin: '\\[.+?\\]\\(.+?\\)', + returnBegin: true, + contains: [ + { + className: 'link_label', + begin: '\\[.+\\]' + }, + { + className: 'link_url', + begin: '\\(', end: '\\)', + excludeBegin: true, excludeEnd: true + } + ] + } + ] + } +}; diff --git a/Scratch/js/highlight/languages/matlab.js b/Scratch/js/highlight/languages/matlab.js new file mode 100644 index 0000000..d162f5a --- /dev/null +++ b/Scratch/js/highlight/languages/matlab.js @@ -0,0 +1,74 @@ +/* +Language: Matlab +Author: Denis Bardadym +*/ + +hljs.LANGUAGES.matlab = { + defaultMode: { + keywords: { + 'keyword': { + 'break': 1, 'case': 1,'catch': 1,'classdef': 1,'continue': 1,'else': 1,'elseif': 1,'end': 1,'enumerated': 1, + 'events': 1,'for': 1,'function': 1,'global': 1,'if': 1,'methods': 1,'otherwise': 1,'parfor': 1, + 'persistent': 1,'properties': 1,'return': 1,'spmd': 1,'switch': 1,'try': 1,'while': 1 + }, + 'built_in': { + 'sin': 1,'sind': 1,'sinh': 1,'asin': 1,'asind': 1,'asinh': 1,'cos': 1,'cosd': 1,'cosh': 1, + 'acos': 1,'acosd': 1,'acosh': 1,'tan': 1,'tand': 1,'tanh': 1,'atan': 1,'atand': 1,'atan2': 1, + 'atanh': 1,'sec': 1,'secd': 1,'sech': 1,'asec': 1,'asecd': 1,'asech': 1,'csc': 1,'cscd': 1, + 'csch': 1,'acsc': 1,'acscd': 1,'acsch': 1,'cot': 1,'cotd': 1,'coth': 1,'acot': 1,'acotd': 1, + 'acoth': 1,'hypot': 1,'exp': 1,'expm1': 1,'log': 1,'log1p': 1,'log10': 1,'log2': 1,'pow2': 1, + 'realpow': 1,'reallog': 1,'realsqrt': 1,'sqrt': 1,'nthroot': 1,'nextpow2': 1,'abs': 1, + 'angle': 1,'complex': 1,'conj': 1,'imag': 1,'real': 1,'unwrap': 1,'isreal': 1,'cplxpair': 1, + 'fix': 1,'floor': 1,'ceil': 1,'round': 1,'mod': 1,'rem': 1,'sign': 1, + 'airy': 1,'besselj': 1,'bessely': 1,'besselh': 1,'besseli': 1,'besselk': 1,'beta': 1, + 'betainc': 1,'betaln': 1,'ellipj': 1,'ellipke': 1,'erf': 1,'erfc': 1,'erfcx': 1, + 'erfinv': 1,'expint': 1,'gamma': 1,'gammainc': 1,'gammaln': 1,'psi': 1,'legendre': 1, + 'cross': 1,'dot': 1,'factor': 1,'isprime': 1,'primes': 1,'gcd': 1,'lcm': 1,'rat': 1, + 'rats': 1,'perms': 1,'nchoosek': 1,'factorial': 1,'cart2sph': 1,'cart2pol': 1, + 'pol2cart': 1,'sph2cart': 1,'hsv2rgb': 1,'rgb2hsv': 1, + 'zeros': 1,'ones': 1,'eye': 1,'repmat': 1,'rand': 1,'randn': 1,'linspace': 1,'logspace': 1, + 'freqspace': 1,'meshgrid': 1,'accumarray': 1,'size': 1,'length': 1,'ndims': 1,'numel': 1, + 'disp': 1,'isempty': 1,'isequal': 1,'isequalwithequalnans': 1,'cat': 1,'reshape': 1, + 'diag': 1,'blkdiag': 1,'tril': 1,'triu': 1,'fliplr': 1,'flipud': 1,'flipdim': 1,'rot90': 1, + 'find': 1,'end': 1,'sub2ind': 1,'ind2sub': 1,'bsxfun': 1,'ndgrid': 1,'permute': 1, + 'ipermute': 1,'shiftdim': 1,'circshift': 1,'squeeze': 1,'isscalar': 1,'isvector': 1, + 'ans': 1,'eps': 1,'realmax': 1,'realmin': 1,'pi': 1,'i': 1,'inf': 1,'nan': 1,'isnan': 1, + 'isinf': 1,'isfinite': 1,'j': 1,'why': 1,'compan': 1,'gallery': 1,'hadamard': 1,'hankel': 1, + 'hilb': 1,'invhilb': 1,'magic': 1,'pascal': 1,'rosser': 1,'toeplitz': 1,'vander': 1, + 'wilkinson': 1 + }, + }, + illegal: '(//|"|#|/\\*|\\s+/\\w+)', + contains: [ + { + className: 'function', + begin: 'function', end: '$', + keywords: {'function': 1}, + contains: [ + { + className: 'title', + begin: hljs.UNDERSCORE_IDENT_RE + }, + { + className: 'params', + begin: '\\(', end: '\\)' + }, + { + className: 'params', + begin: '\\[', end: '\\]' + } + ] + }, + { + className: 'string', + begin: '\'', end: '\'', + contains: [hljs.BACKSLASH_ESCAPE, {begin: '\'\''}] + }, + { + className: 'comment', + begin: '\\%', end: '$' + }, + hljs.C_NUMBER_MODE + ] + } +}; diff --git a/Scratch/js/highlight/languages/mel.js b/Scratch/js/highlight/languages/mel.js new file mode 100644 index 0000000..32a5a33 --- /dev/null +++ b/Scratch/js/highlight/languages/mel.js @@ -0,0 +1,45 @@ +/* +Language: MEL +Description: Maya Embedded Language +Author: Shuen-Huei Guan +*/ + +hljs.LANGUAGES.mel = { + defaultMode: { + keywords: { + 'int': 1, 'float': 1, 'string': 1, 'vector': 1, 'matrix': 1, + 'if': 1, 'else': 1, 'switch': 1, 'case': 1, 'default': 1, 'while': 1, 'do': 1, 'for': 1, 'in': 1, 'break': 1, 'continue': 1, + 'global': 1, 'proc': 1, 'return': 1, + 'about': 1, 'abs': 1, 'addAttr': 1, 'addAttributeEditorNodeHelp': 1, 'addDynamic': 1, 'addNewShelfTab': 1, + 'addPP': 1, 'addPanelCategory': 1, 'addPrefixToName': 1, 'advanceToNextDrivenKey': 1, 'affectedNet': 1, 'affects': 1, + 'aimConstraint': 1, 'air': 1, 'alias': 1, 'aliasAttr': 1, 'align': 1, 'alignCtx': 1, 'alignCurve': 1, 'alignSurface': 1, + 'allViewFit': 1, 'ambientLight': 1, 'angle': 1, 'angleBetween': 1, 'animCone': 1, 'animCurveEditor': 1, 'animDisplay': 1, + 'animView': 1, 'annotate': 1, 'appendStringArray': 1, 'applicationName': 1, 'applyAttrPreset': 1, 'applyTake': 1, 'arcLenDimContext': 1, + 'arcLengthDimension': 1, 'arclen': 1, 'arrayMapper': 1, 'art3dPaintCtx': 1, 'artAttrCtx': 1, 'artAttrPaintVertexCtx': 1, 'artAttrSkinPaintCtx': 1, + 'artAttrTool': 1, 'artBuildPaintMenu': 1, 'artFluidAttrCtx': 1, 'artPuttyCtx': 1, 'artSelectCtx': 1, 'artSetPaintCtx': 1, 'artUserPaintCtx': 1, + 'assignCommand': 1, 'assignInputDevice': 1, 'assignViewportFactories': 1, 'attachCurve': 1, 'attachDeviceAttr': 1, 'attachSurface': 1, 'attrColorSliderGrp': 1, 'attrCompatibility': 1, 'attrControlGrp': 1, 'attrEnumOptionMenu': 1, 'attrEnumOptionMenuGrp': 1, 'attrFieldGrp': 1, 'attrFieldSliderGrp': 1, 'attrNavigationControlGrp': 1, 'attrPresetEditWin': 1, 'attributeExists': 1, 'attributeInfo': 1, 'attributeMenu': 1, 'attributeQuery': 1, 'autoKeyframe': 1, 'autoPlace': 1, 'bakeClip': 1, 'bakeFluidShading': 1, 'bakePartialHistory': 1, 'bakeResults': 1, 'bakeSimulation': 1, 'basename': 1, 'basenameEx': 1, 'batchRender': 1, 'bessel': 1, 'bevel': 1, 'bevelPlus': 1, 'binMembership': 1, 'bindSkin': 1, 'blend2': 1, 'blendShape': 1, 'blendShapeEditor': 1, 'blendShapePanel': 1, 'blendTwoAttr': 1, 'blindDataType': 1, 'boneLattice': 1, 'boundary': 1, 'boxDollyCtx': 1, 'boxZoomCtx': 1, 'bufferCurve': 1, 'buildBookmarkMenu': 1, 'buildKeyframeMenu': 1, 'button': 1, 'buttonManip': 1, 'CBG': 1, 'cacheFile': 1, 'cacheFileCombine': 1, 'cacheFileMerge': 1, 'cacheFileTrack': 1, 'camera': 1, 'cameraView': 1, 'canCreateManip': 1, 'canvas': 1, 'capitalizeString': 1, 'catch': 1, 'catchQuiet': 1, 'ceil': 1, 'changeSubdivComponentDisplayLevel': 1, 'changeSubdivRegion': 1, 'channelBox': 1, 'character': 1, 'characterMap': 1, 'characterOutlineEditor': 1, 'characterize': 1, 'chdir': 1, 'checkBox': 1, 'checkBoxGrp': 1, 'checkDefaultRenderGlobals': 1, 'choice': 1, 'circle': 1, 'circularFillet': 1, 'clamp': 1, 'clear': 1, 'clearCache': 1, 'clip': 1, 'clipEditor': 1, 'clipEditorCurrentTimeCtx': 1, 'clipSchedule': 1, 'clipSchedulerOutliner': 1, 'clipTrimBefore': 1, 'closeCurve': 1, 'closeSurface': 1, 'cluster': 1, 'cmdFileOutput': 1, 'cmdScrollFieldExecuter': 1, 'cmdScrollFieldReporter': 1, 'cmdShell': 1, 'coarsenSubdivSelectionList': 1, 'collision': 1, 'color': 1, 'colorAtPoint': 1, 'colorEditor': 1, 'colorIndex': 1, 'colorIndexSliderGrp': 1, 'colorSliderButtonGrp': 1, 'colorSliderGrp': 1, 'columnLayout': 1, 'commandEcho': 1, 'commandLine': 1, 'commandPort': 1, 'compactHairSystem': 1, 'componentEditor': 1, 'compositingInterop': 1, 'computePolysetVolume': 1, 'condition': 1, 'cone': 1, 'confirmDialog': 1, 'connectAttr': 1, 'connectControl': 1, 'connectDynamic': 1, 'connectJoint': 1, 'connectionInfo': 1, 'constrain': 1, 'constrainValue': 1, 'constructionHistory': 1, 'container': 1, 'containsMultibyte': 1, 'contextInfo': 1, 'control': 1, 'convertFromOldLayers': 1, 'convertIffToPsd': 1, 'convertLightmap': 1, 'convertSolidTx': 1, 'convertTessellation': 1, 'convertUnit': 1, 'copyArray': 1, 'copyFlexor': 1, 'copyKey': 1, 'copySkinWeights': 1, 'cos': 1, 'cpButton': 1, 'cpCache': 1, 'cpClothSet': 1, 'cpCollision': 1, 'cpConstraint': 1, 'cpConvClothToMesh': 1, 'cpForces': 1, 'cpGetSolverAttr': 1, 'cpPanel': 1, 'cpProperty': 1, 'cpRigidCollisionFilter': 1, 'cpSeam': 1, 'cpSetEdit': 1, 'cpSetSolverAttr': 1, 'cpSolver': 1, 'cpSolverTypes': 1, 'cpTool': 1, 'cpUpdateClothUVs': 1, 'createDisplayLayer': 1, 'createDrawCtx': 1, 'createEditor': 1, 'createLayeredPsdFile': 1, 'createMotionField': 1, 'createNewShelf': 1, 'createNode': 1, 'createRenderLayer': 1, 'createSubdivRegion': 1, 'cross': 1, 'crossProduct': 1, 'ctxAbort': 1, 'ctxCompletion': 1, 'ctxEditMode': 1, 'ctxTraverse': 1, 'currentCtx': 1, 'currentTime': 1, 'currentTimeCtx': 1, 'currentUnit': 1, 'currentUnit': 1, 'curve': 1, 'curveAddPtCtx': 1, 'curveCVCtx': 1, 'curveEPCtx': 1, 'curveEditorCtx': 1, 'curveIntersect': 1, 'curveMoveEPCtx': 1, 'curveOnSurface': 1, 'curveSketchCtx': 1, 'cutKey': 1, 'cycleCheck': 1, 'cylinder': 1, 'dagPose': 1, 'date': 1, 'defaultLightListCheckBox': 1, 'defaultNavigation': 1, 'defineDataServer': 1, 'defineVirtualDevice': 1, 'deformer': 1, 'deg_to_rad': 1, 'delete': 1, 'deleteAttr': 1, 'deleteShadingGroupsAndMaterials': 1, 'deleteShelfTab': 1, 'deleteUI': 1, 'deleteUnusedBrushes': 1, 'delrandstr': 1, 'detachCurve': 1, 'detachDeviceAttr': 1, 'detachSurface': 1, 'deviceEditor': 1, 'devicePanel': 1, 'dgInfo': 1, 'dgdirty': 1, 'dgeval': 1, 'dgtimer': 1, 'dimWhen': 1, 'directKeyCtx': 1, 'directionalLight': 1, 'dirmap': 1, 'dirname': 1, 'disable': 1, 'disconnectAttr': 1, 'disconnectJoint': 1, 'diskCache': 1, 'displacementToPoly': 1, 'displayAffected': 1, 'displayColor': 1, 'displayCull': 1, 'displayLevelOfDetail': 1, 'displayPref': 1, 'displayRGBColor': 1, 'displaySmoothness': 1, 'displayStats': 1, 'displayString': 1, 'displaySurface': 1, 'distanceDimContext': 1, 'distanceDimension': 1, 'doBlur': 1, 'dolly': 1, 'dollyCtx': 1, 'dopeSheetEditor': 1, 'dot': 1, 'dotProduct': 1, 'doubleProfileBirailSurface': 1, 'drag': 1, 'dragAttrContext': 1, 'draggerContext': 1, 'dropoffLocator': 1, 'duplicate': 1, 'duplicateCurve': 1, 'duplicateSurface': 1, 'dynCache': 1, 'dynControl': 1, 'dynExport': 1, 'dynExpression': 1, 'dynGlobals': 1, 'dynPaintEditor': 1, 'dynParticleCtx': 1, 'dynPref': 1, 'dynRelEdPanel': 1, 'dynRelEditor': 1, 'dynamicLoad': 1, 'editAttrLimits': 1, 'editDisplayLayerGlobals': 1, 'editDisplayLayerMembers': 1, 'editRenderLayerAdjustment': 1, 'editRenderLayerGlobals': 1, 'editRenderLayerMembers': 1, 'editor': 1, 'editorTemplate': 1, 'effector': 1, 'emit': 1, 'emitter': 1, 'enableDevice': 1, 'encodeString': 1, 'endString': 1, 'endsWith': 1, 'env': 1, 'equivalent': 1, 'equivalentTol': 1, 'erf': 1, 'error': 1, 'eval': 1, 'eval': 1, 'evalDeferred': 1, 'evalEcho': 1, 'event': 1, 'exactWorldBoundingBox': 1, 'exclusiveLightCheckBox': 1, 'exec': 1, 'executeForEachObject': 1, 'exists': 1, 'exp': 1, 'expression': 1, 'expressionEditorListen': 1, 'extendCurve': 1, 'extendSurface': 1, 'extrude': 1, 'fcheck': 1, 'fclose': 1, 'feof': 1, 'fflush': 1, 'fgetline': 1, 'fgetword': 1, 'file': 1, 'fileBrowserDialog': 1, 'fileDialog': 1, 'fileExtension': 1, 'fileInfo': 1, 'filetest': 1, 'filletCurve': 1, 'filter': 1, 'filterCurve': 1, 'filterExpand': 1, 'filterStudioImport': 1, 'findAllIntersections': 1, 'findAnimCurves': 1, 'findKeyframe': 1, 'findMenuItem': 1, 'findRelatedSkinCluster': 1, 'finder': 1, 'firstParentOf': 1, 'fitBspline': 1, 'flexor': 1, 'floatEq': 1, 'floatField': 1, 'floatFieldGrp': 1, 'floatScrollBar': 1, 'floatSlider': 1, 'floatSlider2': 1, 'floatSliderButtonGrp': 1, 'floatSliderGrp': 1, 'floor': 1, 'flow': 1, 'fluidCacheInfo': 1, 'fluidEmitter': 1, 'fluidVoxelInfo': 1, 'flushUndo': 1, 'fmod': 1, 'fontDialog': 1, 'fopen': 1, 'formLayout': 1, 'format': 1, 'fprint': 1, 'frameLayout': 1, 'fread': 1, 'freeFormFillet': 1, 'frewind': 1, 'fromNativePath': 1, 'fwrite': 1, 'gamma': 1, 'gauss': 1, 'geometryConstraint': 1, 'getApplicationVersionAsFloat': 1, 'getAttr': 1, 'getClassification': 1, 'getDefaultBrush': 1, 'getFileList': 1, 'getFluidAttr': 1, 'getInputDeviceRange': 1, 'getMayaPanelTypes': 1, 'getModifiers': 1, 'getPanel': 1, 'getParticleAttr': 1, 'getPluginResource': 1, 'getenv': 1, 'getpid': 1, 'glRender': 1, 'glRenderEditor': 1, 'globalStitch': 1, 'gmatch': 1, 'goal': 1, 'gotoBindPose': 1, 'grabColor': 1, 'gradientControl': 1, 'gradientControlNoAttr': 1, 'graphDollyCtx': 1, 'graphSelectContext': 1, 'graphTrackCtx': 1, 'gravity': 1, 'grid': 1, 'gridLayout': 1, 'group': 1, 'groupObjectsByName': 1, 'HfAddAttractorToAS': 1, 'HfAssignAS': 1, 'HfBuildEqualMap': 1, 'HfBuildFurFiles': 1, 'HfBuildFurImages': 1, 'HfCancelAFR': 1, 'HfConnectASToHF': 1, 'HfCreateAttractor': 1, 'HfDeleteAS': 1, 'HfEditAS': 1, 'HfPerformCreateAS': 1, 'HfRemoveAttractorFromAS': 1, 'HfSelectAttached': 1, 'HfSelectAttractors': 1, 'HfUnAssignAS': 1, 'hardenPointCurve': 1, 'hardware': 1, 'hardwareRenderPanel': 1, 'headsUpDisplay': 1, 'headsUpMessage': 1, 'help': 1, 'helpLine': 1, 'hermite': 1, 'hide': 1, 'hilite': 1, 'hitTest': 1, 'hotBox': 1, 'hotkey': 1, 'hotkeyCheck': 1, 'hsv_to_rgb': 1, 'hudButton': 1, 'hudSlider': 1, 'hudSliderButton': 1, 'hwReflectionMap': 1, 'hwRender': 1, 'hwRenderLoad': 1, 'hyperGraph': 1, 'hyperPanel': 1, 'hyperShade': 1, 'hypot': 1, 'iconTextButton': 1, 'iconTextCheckBox': 1, 'iconTextRadioButton': 1, 'iconTextRadioCollection': 1, 'iconTextScrollList': 1, 'iconTextStaticLabel': 1, 'ikHandle': 1, 'ikHandleCtx': 1, 'ikHandleDisplayScale': 1, 'ikSolver': 1, 'ikSplineHandleCtx': 1, 'ikSystem': 1, 'ikSystemInfo': 1, 'ikfkDisplayMethod': 1, 'illustratorCurves': 1, 'image': 1, 'imfPlugins': 1, 'inheritTransform': 1, 'insertJoint': 1, 'insertJointCtx': 1, 'insertKeyCtx': 1, 'insertKnotCurve': 1, 'insertKnotSurface': 1, 'instance': 1, 'instanceable': 1, 'instancer': 1, 'intField': 1, 'intFieldGrp': 1, 'intScrollBar': 1, 'intSlider': 1, 'intSliderGrp': 1, 'interToUI': 1, 'internalVar': 1, 'intersect': 1, 'iprEngine': 1, 'isAnimCurve': 1, 'isConnected': 1, 'isDirty': 1, 'isParentOf': 1, 'isSameObject': 1, 'isTrue': 1, 'isValidObjectName': 1, 'isValidString': 1, 'isValidUiName': 1, 'isolateSelect': 1, 'itemFilter': 1, 'itemFilterAttr': 1, 'itemFilterRender': 1, 'itemFilterType': 1, 'joint': 1, 'jointCluster': 1, 'jointCtx': 1, 'jointDisplayScale': 1, 'jointLattice': 1, 'keyTangent': 1, 'keyframe': 1, 'keyframeOutliner': 1, 'keyframeRegionCurrentTimeCtx': 1, 'keyframeRegionDirectKeyCtx': 1, 'keyframeRegionDollyCtx': 1, 'keyframeRegionInsertKeyCtx': 1, 'keyframeRegionMoveKeyCtx': 1, 'keyframeRegionScaleKeyCtx': 1, 'keyframeRegionSelectKeyCtx': 1, 'keyframeRegionSetKeyCtx': 1, 'keyframeRegionTrackCtx': 1, 'keyframeStats': 1, 'lassoContext': 1, 'lattice': 1, 'latticeDeformKeyCtx': 1, 'launch': 1, 'launchImageEditor': 1, 'layerButton': 1, 'layeredShaderPort': 1, 'layeredTexturePort': 1, 'layout': 1, 'layoutDialog': 1, 'lightList': 1, 'lightListEditor': 1, 'lightListPanel': 1, 'lightlink': 1, 'lineIntersection': 1, 'linearPrecision': 1, 'linstep': 1, 'listAnimatable': 1, 'listAttr': 1, 'listCameras': 1, 'listConnections': 1, 'listDeviceAttachments': 1, 'listHistory': 1, 'listInputDeviceAxes': 1, 'listInputDeviceButtons': 1, 'listInputDevices': 1, 'listMenuAnnotation': 1, 'listNodeTypes': 1, 'listPanelCategories': 1, 'listRelatives': 1, 'listSets': 1, 'listTransforms': 1, 'listUnselected': 1, 'listerEditor': 1, 'loadFluid': 1, 'loadNewShelf': 1, 'loadPlugin': 1, 'loadPluginLanguageResources': 1, 'loadPrefObjects': 1, 'localizedPanelLabel': 1, 'lockNode': 1, 'loft': 1, 'log': 1, 'longNameOf': 1, 'lookThru': 1, 'ls': 1, 'lsThroughFilter': 1, 'lsType': 1, 'lsUI': 1, 'Mayatomr': 1, 'mag': 1, 'makeIdentity': 1, 'makeLive': 1, 'makePaintable': 1, 'makeRoll': 1, 'makeSingleSurface': 1, 'makeTubeOn': 1, 'makebot': 1, 'manipMoveContext': 1, 'manipMoveLimitsCtx': 1, 'manipOptions': 1, 'manipRotateContext': 1, 'manipRotateLimitsCtx': 1, 'manipScaleContext': 1, 'manipScaleLimitsCtx': 1, 'marker': 1, 'match': 1, 'max': 1, 'memory': 1, 'menu': 1, 'menuBarLayout': 1, 'menuEditor': 1, 'menuItem': 1, 'menuItemToShelf': 1, 'menuSet': 1, 'menuSetPref': 1, 'messageLine': 1, 'min': 1, 'minimizeApp': 1, 'mirrorJoint': 1, 'modelCurrentTimeCtx': 1, 'modelEditor': 1, 'modelPanel': 1, 'mouse': 1, 'movIn': 1, 'movOut': 1, 'move': 1, 'moveIKtoFK': 1, 'moveKeyCtx': 1, 'moveVertexAlongDirection': 1, 'multiProfileBirailSurface': 1, 'mute': 1, 'nParticle': 1, 'nameCommand': 1, 'nameField': 1, 'namespace': 1, 'namespaceInfo': 1, 'newPanelItems': 1, 'newton': 1, 'nodeCast': 1, 'nodeIconButton': 1, 'nodeOutliner': 1, 'nodePreset': 1, 'nodeType': 1, 'noise': 1, 'nonLinear': 1, 'normalConstraint': 1, 'normalize': 1, 'nurbsBoolean': 1, 'nurbsCopyUVSet': 1, 'nurbsCube': 1, 'nurbsEditUV': 1, 'nurbsPlane': 1, 'nurbsSelect': 1, 'nurbsSquare': 1, 'nurbsToPoly': 1, 'nurbsToPolygonsPref': 1, 'nurbsToSubdiv': 1, 'nurbsToSubdivPref': 1, 'nurbsUVSet': 1, 'nurbsViewDirectionVector': 1, 'objExists': 1, 'objectCenter': 1, 'objectLayer': 1, 'objectType': 1, 'objectTypeUI': 1, 'obsoleteProc': 1, 'oceanNurbsPreviewPlane': 1, 'offsetCurve': 1, 'offsetCurveOnSurface': 1, 'offsetSurface': 1, 'openGLExtension': 1, 'openMayaPref': 1, 'optionMenu': 1, 'optionMenuGrp': 1, 'optionVar': 1, 'orbit': 1, 'orbitCtx': 1, 'orientConstraint': 1, 'outlinerEditor': 1, 'outlinerPanel': 1, 'overrideModifier': 1, 'paintEffectsDisplay': 1, 'pairBlend': 1, 'palettePort': 1, 'paneLayout': 1, 'panel': 1, 'panelConfiguration': 1, 'panelHistory': 1, 'paramDimContext': 1, 'paramDimension': 1, 'paramLocator': 1, 'parent': 1, 'parentConstraint': 1, 'particle': 1, 'particleExists': 1, 'particleInstancer': 1, 'particleRenderInfo': 1, 'partition': 1, 'pasteKey': 1, 'pathAnimation': 1, 'pause': 1, 'pclose': 1, 'percent': 1, 'performanceOptions': 1, 'pfxstrokes': 1, 'pickWalk': 1, 'picture': 1, 'pixelMove': 1, 'planarSrf': 1, 'plane': 1, 'play': 1, 'playbackOptions': 1, 'playblast': 1, 'plugAttr': 1, 'plugNode': 1, 'pluginInfo': 1, 'pluginResourceUtil': 1, 'pointConstraint': 1, 'pointCurveConstraint': 1, 'pointLight': 1, 'pointMatrixMult': 1, 'pointOnCurve': 1, 'pointOnSurface': 1, 'pointPosition': 1, 'poleVectorConstraint': 1, 'polyAppend': 1, 'polyAppendFacetCtx': 1, 'polyAppendVertex': 1, 'polyAutoProjection': 1, 'polyAverageNormal': 1, 'polyAverageVertex': 1, 'polyBevel': 1, 'polyBlendColor': 1, 'polyBlindData': 1, 'polyBoolOp': 1, 'polyBridgeEdge': 1, 'polyCacheMonitor': 1, 'polyCheck': 1, 'polyChipOff': 1, 'polyClipboard': 1, 'polyCloseBorder': 1, 'polyCollapseEdge': 1, 'polyCollapseFacet': 1, 'polyColorBlindData': 1, 'polyColorDel': 1, 'polyColorPerVertex': 1, 'polyColorSet': 1, 'polyCompare': 1, 'polyCone': 1, 'polyCopyUV': 1, 'polyCrease': 1, 'polyCreaseCtx': 1, 'polyCreateFacet': 1, 'polyCreateFacetCtx': 1, 'polyCube': 1, 'polyCut': 1, 'polyCutCtx': 1, 'polyCylinder': 1, 'polyCylindricalProjection': 1, 'polyDelEdge': 1, 'polyDelFacet': 1, 'polyDelVertex': 1, 'polyDuplicateAndConnect': 1, 'polyDuplicateEdge': 1, 'polyEditUV': 1, 'polyEditUVShell': 1, 'polyEvaluate': 1, 'polyExtrudeEdge': 1, 'polyExtrudeFacet': 1, 'polyExtrudeVertex': 1, 'polyFlipEdge': 1, 'polyFlipUV': 1, 'polyForceUV': 1, 'polyGeoSampler': 1, 'polyHelix': 1, 'polyInfo': 1, 'polyInstallAction': 1, 'polyLayoutUV': 1, 'polyListComponentConversion': 1, 'polyMapCut': 1, 'polyMapDel': 1, 'polyMapSew': 1, 'polyMapSewMove': 1, 'polyMergeEdge': 1, 'polyMergeEdgeCtx': 1, 'polyMergeFacet': 1, 'polyMergeFacetCtx': 1, 'polyMergeUV': 1, 'polyMergeVertex': 1, 'polyMirrorFace': 1, 'polyMoveEdge': 1, 'polyMoveFacet': 1, 'polyMoveFacetUV': 1, 'polyMoveUV': 1, 'polyMoveVertex': 1, 'polyNormal': 1, 'polyNormalPerVertex': 1, 'polyNormalizeUV': 1, 'polyOptUvs': 1, 'polyOptions': 1, 'polyOutput': 1, 'polyPipe': 1, 'polyPlanarProjection': 1, 'polyPlane': 1, 'polyPlatonicSolid': 1, 'polyPoke': 1, 'polyPrimitive': 1, 'polyPrism': 1, 'polyProjection': 1, 'polyPyramid': 1, 'polyQuad': 1, 'polyQueryBlindData': 1, 'polyReduce': 1, 'polySelect': 1, 'polySelectConstraint': 1, 'polySelectConstraintMonitor': 1, 'polySelectCtx': 1, 'polySelectEditCtx': 1, 'polySeparate': 1, 'polySetToFaceNormal': 1, 'polySewEdge': 1, 'polyShortestPathCtx': 1, 'polySmooth': 1, 'polySoftEdge': 1, 'polySphere': 1, 'polySphericalProjection': 1, 'polySplit': 1, 'polySplitCtx': 1, 'polySplitEdge': 1, 'polySplitRing': 1, 'polySplitVertex': 1, 'polyStraightenUVBorder': 1, 'polySubdivideEdge': 1, 'polySubdivideFacet': 1, 'polyToSubdiv': 1, 'polyTorus': 1, 'polyTransfer': 1, 'polyTriangulate': 1, 'polyUVSet': 1, 'polyUnite': 1, 'polyWedgeFace': 1, 'popen': 1, 'popupMenu': 1, 'pose': 1, 'pow': 1, 'preloadRefEd': 1, 'print': 1, 'progressBar': 1, 'progressWindow': 1, 'projFileViewer': 1, 'projectCurve': 1, 'projectTangent': 1, 'projectionContext': 1, 'projectionManip': 1, 'promptDialog': 1, 'propModCtx': 1, 'propMove': 1, 'psdChannelOutliner': 1, 'psdEditTextureFile': 1, 'psdExport': 1, 'psdTextureFile': 1, 'putenv': 1, 'pwd': 1, 'python': 1, 'querySubdiv': 1, 'quit': 1, 'rad_to_deg': 1, 'radial': 1, 'radioButton': 1, 'radioButtonGrp': 1, 'radioCollection': 1, 'radioMenuItemCollection': 1, 'rampColorPort': 1, 'rand': 1, 'randomizeFollicles': 1, 'randstate': 1, 'rangeControl': 1, 'readTake': 1, 'rebuildCurve': 1, 'rebuildSurface': 1, 'recordAttr': 1, 'recordDevice': 1, 'redo': 1, 'reference': 1, 'referenceEdit': 1, 'referenceQuery': 1, 'refineSubdivSelectionList': 1, 'refresh': 1, 'refreshAE': 1, 'registerPluginResource': 1, 'rehash': 1, 'reloadImage': 1, 'removeJoint': 1, 'removeMultiInstance': 1, 'removePanelCategory': 1, 'rename': 1, 'renameAttr': 1, 'renameSelectionList': 1, 'renameUI': 1, 'render': 1, 'renderGlobalsNode': 1, 'renderInfo': 1, 'renderLayerButton': 1, 'renderLayerParent': 1, 'renderLayerPostProcess': 1, 'renderLayerUnparent': 1, 'renderManip': 1, 'renderPartition': 1, 'renderQualityNode': 1, 'renderSettings': 1, 'renderThumbnailUpdate': 1, 'renderWindowEditor': 1, 'renderWindowSelectContext': 1, 'renderer': 1, 'reorder': 1, 'reorderDeformers': 1, 'requires': 1, 'reroot': 1, 'resampleFluid': 1, 'resetAE': 1, 'resetPfxToPolyCamera': 1, 'resetTool': 1, 'resolutionNode': 1, 'retarget': 1, 'reverseCurve': 1, 'reverseSurface': 1, 'revolve': 1, 'rgb_to_hsv': 1, 'rigidBody': 1, 'rigidSolver': 1, 'roll': 1, 'rollCtx': 1, 'rootOf': 1, 'rot': 1, 'rotate': 1, 'rotationInterpolation': 1, 'roundConstantRadius': 1, 'rowColumnLayout': 1, 'rowLayout': 1, 'runTimeCommand': 1, 'runup': 1, 'sampleImage': 1, 'saveAllShelves': 1, 'saveAttrPreset': 1, 'saveFluid': 1, 'saveImage': 1, 'saveInitialState': 1, 'saveMenu': 1, 'savePrefObjects': 1, 'savePrefs': 1, 'saveShelf': 1, 'saveToolSettings': 1, 'scale': 1, 'scaleBrushBrightness': 1, 'scaleComponents': 1, 'scaleConstraint': 1, 'scaleKey': 1, 'scaleKeyCtx': 1, 'sceneEditor': 1, 'sceneUIReplacement': 1, 'scmh': 1, 'scriptCtx': 1, 'scriptEditorInfo': 1, 'scriptJob': 1, 'scriptNode': 1, 'scriptTable': 1, 'scriptToShelf': 1, 'scriptedPanel': 1, 'scriptedPanelType': 1, 'scrollField': 1, 'scrollLayout': 1, 'sculpt': 1, 'searchPathArray': 1, 'seed': 1, 'selLoadSettings': 1, 'select': 1, 'selectContext': 1, 'selectCurveCV': 1, 'selectKey': 1, 'selectKeyCtx': 1, 'selectKeyframeRegionCtx': 1, 'selectMode': 1, 'selectPref': 1, 'selectPriority': 1, 'selectType': 1, 'selectedNodes': 1, 'selectionConnection': 1, 'separator': 1, 'setAttr': 1, 'setAttrEnumResource': 1, 'setAttrMapping': 1, 'setAttrNiceNameResource': 1, 'setConstraintRestPosition': 1, 'setDefaultShadingGroup': 1, 'setDrivenKeyframe': 1, 'setDynamic': 1, 'setEditCtx': 1, 'setEditor': 1, 'setFluidAttr': 1, 'setFocus': 1, 'setInfinity': 1, 'setInputDeviceMapping': 1, 'setKeyCtx': 1, 'setKeyPath': 1, 'setKeyframe': 1, 'setKeyframeBlendshapeTargetWts': 1, 'setMenuMode': 1, 'setNodeNiceNameResource': 1, 'setNodeTypeFlag': 1, 'setParent': 1, 'setParticleAttr': 1, 'setPfxToPolyCamera': 1, 'setPluginResource': 1, 'setProject': 1, 'setStampDensity': 1, 'setStartupMessage': 1, 'setState': 1, 'setToolTo': 1, 'setUITemplate': 1, 'setXformManip': 1, 'sets': 1, 'shadingConnection': 1, 'shadingGeometryRelCtx': 1, 'shadingLightRelCtx': 1, 'shadingNetworkCompare': 1, 'shadingNode': 1, 'shapeCompare': 1, 'shelfButton': 1, 'shelfLayout': 1, 'shelfTabLayout': 1, 'shellField': 1, 'shortNameOf': 1, 'showHelp': 1, 'showHidden': 1, 'showManipCtx': 1, 'showSelectionInTitle': 1, 'showShadingGroupAttrEditor': 1, 'showWindow': 1, 'sign': 1, 'simplify': 1, 'sin': 1, 'singleProfileBirailSurface': 1, 'size': 1, 'sizeBytes': 1, 'skinCluster': 1, 'skinPercent': 1, 'smoothCurve': 1, 'smoothTangentSurface': 1, 'smoothstep': 1, 'snap2to2': 1, 'snapKey': 1, 'snapMode': 1, 'snapTogetherCtx': 1, 'snapshot': 1, 'soft': 1, 'softMod': 1, 'softModCtx': 1, 'sort': 1, 'sound': 1, 'soundControl': 1, 'source': 1, 'spaceLocator': 1, 'sphere': 1, 'sphrand': 1, 'spotLight': 1, 'spotLightPreviewPort': 1, 'spreadSheetEditor': 1, 'spring': 1, 'sqrt': 1, 'squareSurface': 1, 'srtContext': 1, 'stackTrace': 1, 'startString': 1, 'startsWith': 1, 'stitchAndExplodeShell': 1, 'stitchSurface': 1, 'stitchSurfacePoints': 1, 'strcmp': 1, 'stringArrayCatenate': 1, 'stringArrayContains': 1, 'stringArrayCount': 1, 'stringArrayInsertAtIndex': 1, 'stringArrayIntersector': 1, 'stringArrayRemove': 1, 'stringArrayRemoveAtIndex': 1, 'stringArrayRemoveDuplicates': 1, 'stringArrayRemoveExact': 1, 'stringArrayToString': 1, 'stringToStringArray': 1, 'strip': 1, 'stripPrefixFromName': 1, 'stroke': 1, 'subdAutoProjection': 1, 'subdCleanTopology': 1, 'subdCollapse': 1, 'subdDuplicateAndConnect': 1, 'subdEditUV': 1, 'subdListComponentConversion': 1, 'subdMapCut': 1, 'subdMapSewMove': 1, 'subdMatchTopology': 1, 'subdMirror': 1, 'subdToBlind': 1, 'subdToPoly': 1, 'subdTransferUVsToCache': 1, 'subdiv': 1, 'subdivCrease': 1, 'subdivDisplaySmoothness': 1, 'substitute': 1, 'substituteAllString': 1, 'substituteGeometry': 1, 'substring': 1, 'surface': 1, 'surfaceSampler': 1, 'surfaceShaderList': 1, 'swatchDisplayPort': 1, 'switchTable': 1, 'symbolButton': 1, 'symbolCheckBox': 1, 'sysFile': 1, 'system': 1, 'tabLayout': 1, 'tan': 1, 'tangentConstraint': 1, 'texLatticeDeformContext': 1, 'texManipContext': 1, 'texMoveContext': 1, 'texMoveUVShellContext': 1, 'texRotateContext': 1, 'texScaleContext': 1, 'texSelectContext': 1, 'texSelectShortestPathCtx': 1, 'texSmudgeUVContext': 1, 'texWinToolCtx': 1, 'text': 1, 'textCurves': 1, 'textField': 1, 'textFieldButtonGrp': 1, 'textFieldGrp': 1, 'textManip': 1, 'textScrollList': 1, 'textToShelf': 1, 'textureDisplacePlane': 1, 'textureHairColor': 1, 'texturePlacementContext': 1, 'textureWindow': 1, 'threadCount': 1, 'threePointArcCtx': 1, 'timeControl': 1, 'timePort': 1, 'timerX': 1, 'toNativePath': 1, 'toggle': 1, 'toggleAxis': 1, 'toggleWindowVisibility': 1, 'tokenize': 1, 'tokenizeList': 1, 'tolerance': 1, 'tolower': 1, 'toolButton': 1, 'toolCollection': 1, 'toolDropped': 1, 'toolHasOptions': 1, 'toolPropertyWindow': 1, 'torus': 1, 'toupper': 1, 'trace': 1, 'track': 1, 'trackCtx': 1, 'transferAttributes': 1, 'transformCompare': 1, 'transformLimits': 1, 'translator': 1, 'trim': 1, 'trunc': 1, 'truncateFluidCache': 1, 'truncateHairCache': 1, 'tumble': 1, 'tumbleCtx': 1, 'turbulence': 1, 'twoPointArcCtx': 1, 'uiRes': 1, 'uiTemplate': 1, 'unassignInputDevice': 1, 'undo': 1, 'undoInfo': 1, 'ungroup': 1, 'uniform': 1, 'unit': 1, 'unloadPlugin': 1, 'untangleUV': 1, 'untitledFileName': 1, 'untrim': 1, 'upAxis': 1, 'updateAE': 1, 'userCtx': 1, 'uvLink': 1, 'uvSnapshot': 1, 'validateShelfName': 1, 'vectorize': 1, 'view2dToolCtx': 1, 'viewCamera': 1, 'viewClipPlane': 1, 'viewFit': 1, 'viewHeadOn': 1, 'viewLookAt': 1, 'viewManip': 1, 'viewPlace': 1, 'viewSet': 1, 'visor': 1, 'volumeAxis': 1, 'vortex': 1, 'waitCursor': 1, 'warning': 1, 'webBrowser': 1, 'webBrowserPrefs': 1, 'whatIs': 1, 'window': 1, 'windowPref': 1, 'wire': 1, 'wireContext': 1, 'workspace': 1, 'wrinkle': 1, 'wrinkleContext': 1, 'writeTake': 1, 'xbmLangPathList': 1, 'xform': 1 + }, + illegal: ' +*/ + +hljs.LANGUAGES.nginx = function() { + var VAR1 = { + className: 'variable', + begin: '\\$\\d+' + }; + var VAR2 = { + className: 'variable', + begin: '\\${', end: '}' + }; + var VAR3 = { + className: 'variable', + begin: '[\\$\\@]' + hljs.UNDERSCORE_IDENT_RE + }; + + return { + defaultMode: { + contains: [ + hljs.HASH_COMMENT_MODE, + { // directive + begin: hljs.UNDERSCORE_IDENT_RE, end: ';|{', returnEnd: true, + keywords: { + accept_mutex: 1, accept_mutex_delay: 1, access_log: 1, + add_after_body: 1, add_before_body: 1, add_header: 1, + addition_types: 1, alias: 1, allow: 1, ancient_browser: 1, + ancient_browser_value: 1, auth_basic: 1, auth_basic_user_file: 1, + autoindex: 1, autoindex_exact_size: 1, autoindex_localtime: 1, + 'break': 1, charset: 1, charset_map: 1, + charset_types: 1, client_body_buffer_size: 1, + client_body_in_file_only: 1, client_body_in_single_buffer: 1, + client_body_temp_path: 1, client_body_timeout: 1, + client_header_buffer_size: 1, client_header_timeout: 1, + client_max_body_size: 1, connection_pool_size: 1, connections: 1, + create_full_put_path: 1, daemon: 1, dav_access: 1, dav_methods: 1, + debug_connection: 1, debug_points: 1, default_type: 1, deny: 1, + directio: 1, directio_alignment: 1, echo: 1, echo_after_body: 1, + echo_before_body: 1, echo_blocking_sleep: 1, echo_duplicate: 1, + echo_end: 1, echo_exec: 1, echo_flush: 1, echo_foreach_split: 1, + echo_location: 1, echo_location_async: 1, echo_read_request_body: 1, + echo_request_body: 1, echo_reset_timer: 1, echo_sleep: 1, + echo_subrequest: 1, echo_subrequest_async: 1, empty_gif: 1, + env: 1, error_log: 1, error_page: 1, + events: 1, expires: 1, fastcgi_bind: 1, fastcgi_buffer_size: 1, + fastcgi_buffers: 1, fastcgi_busy_buffers_size: 1, fastcgi_cache: 1, + fastcgi_cache_key: 1, fastcgi_cache_methods: 1, + fastcgi_cache_min_uses: 1, fastcgi_cache_path: 1, + fastcgi_cache_use_stale: 1, fastcgi_cache_valid: 1, + fastcgi_catch_stderr: 1, fastcgi_connect_timeout: 1, + fastcgi_hide_header: 1, fastcgi_ignore_client_abort: 1, + fastcgi_ignore_headers: 1, fastcgi_index: 1, + fastcgi_intercept_errors: 1, fastcgi_max_temp_file_size: 1, + fastcgi_next_upstream: 1, fastcgi_param: 1, fastcgi_pass: 1, + fastcgi_pass_header: 1, fastcgi_pass_request_body: 1, + fastcgi_pass_request_headers: 1, fastcgi_read_timeout: 1, + fastcgi_send_lowat: 1, fastcgi_send_timeout: 1, + fastcgi_split_path_info: 1, fastcgi_store: 1, fastcgi_store_access: 1, + fastcgi_temp_file_write_size: 1, fastcgi_temp_path: 1, + fastcgi_upstream_fail_timeout: 1, fastcgi_upstream_max_fails: 1, + flv: 1, geo: 1, geoip_city: 1, geoip_country: 1, gzip: 1, + gzip_buffers: 1, gzip_comp_level: 1, gzip_disable: 1, gzip_hash: 1, + gzip_http_version: 1, gzip_min_length: 1, gzip_no_buffer: 1, + gzip_proxied: 1, gzip_static: 1, gzip_types: 1, gzip_vary: 1, + gzip_window: 1, http: 1, 'if': 1, if_modified_since: 1, + ignore_invalid_headers: 1, image_filter: 1, image_filter_buffer: 1, + image_filter_jpeg_quality: 1, image_filter_transparency: 1, include: 1, + index: 1, internal: 1, ip_hash: 1, js: 1, js_load: 1, js_require: 1, + js_utf8: 1, keepalive_requests: 1, keepalive_timeout: 1, + kqueue_changes: 1, kqueue_events: 1, large_client_header_buffers: 1, + limit_conn: 1, limit_conn_log_level: 1, limit_except: 1, limit_rate: 1, + limit_rate_after: 1, limit_req: 1, limit_req_log_level: 1, + limit_req_zone: 1, limit_zone: 1, lingering_time: 1, + lingering_timeout: 1, listen: 1, location: 1, lock_file: 1, + log_format: 1, log_not_found: 1, log_subrequest: 1, map: 1, + map_hash_bucket_size: 1, map_hash_max_size: 1, master_process: 1, + memcached_bind: 1, memcached_buffer_size: 1, + memcached_connect_timeout: 1, memcached_next_upstream: 1, + memcached_pass: 1, memcached_read_timeout: 1, + memcached_send_timeout: 1, memcached_upstream_fail_timeout: 1, + memcached_upstream_max_fails: 1, merge_slashes: 1, min_delete_depth: 1, + modern_browser: 1, modern_browser_value: 1, more_clear_headers: 1, + more_clear_input_headers: 1, more_set_headers: 1, + more_set_input_headers: 1, msie_padding: 1, msie_refresh: 1, + multi_accept: 1, open_file_cache: 1, open_file_cache_errors: 1, + open_file_cache_events: 1, open_file_cache_min_uses: 1, + open_file_cache_retest: 1, open_file_cache_valid: 1, + open_log_file_cache: 1, optimize_server_names: 1, output_buffers: 1, + override_charset: 1, perl: 1, perl_modules: 1, + perl_require: 1, perl_set: 1, pid: 1, port_in_redirect: 1, + post_action: 1, postpone_gzipping: 1, postpone_output: 1, + proxy_bind: 1, proxy_buffer_size: 1, proxy_buffering: 1, + proxy_buffers: 1, proxy_busy_buffers_size: 1, proxy_cache: 1, + proxy_cache_key: 1, proxy_cache_methods: 1, proxy_cache_min_uses: 1, + proxy_cache_path: 1, proxy_cache_use_stale: 1, proxy_cache_valid: 1, + proxy_connect_timeout: 1, proxy_headers_hash_bucket_size: 1, + proxy_headers_hash_max_size: 1, proxy_hide_header: 1, + proxy_ignore_client_abort: 1, proxy_ignore_headers: 1, + proxy_intercept_errors: 1, proxy_max_temp_file_size: 1, + proxy_method: 1, proxy_next_upstream: 1, proxy_pass: 1, + proxy_pass_header: 1, proxy_pass_request_body: 1, + proxy_pass_request_headers: 1, proxy_read_timeout: 1, + proxy_redirect: 1, proxy_send_lowat: 1, proxy_send_timeout: 1, + proxy_set_body: 1, proxy_set_header: 1, proxy_store: 1, + proxy_store_access: 1, proxy_temp_file_write_size: 1, + proxy_temp_path: 1, proxy_upstream_fail_timeout: 1, + proxy_upstream_max_fails: 1, push_authorized_channels_only: 1, + push_channel_group: 1, push_max_channel_id_length: 1, + push_max_channel_subscribers: 1, push_max_message_buffer_length: 1, + push_max_reserved_memory: 1, push_message_buffer_length: 1, + push_message_timeout: 1, push_min_message_buffer_length: 1, + push_min_message_recipients: 1, push_publisher: 1, + push_store_messages: 1, push_subscriber: 1, + push_subscriber_concurrency: 1, random_index: 1, read_ahead: 1, + real_ip_header: 1, recursive_error_pages: 1, request_pool_size: 1, + reset_timedout_connection: 1, resolver: 1, resolver_timeout: 1, + 'return': 1, rewrite: 1, rewrite_log: 1, root: 1, satisfy: 1, + satisfy_any: 1, send_lowat: 1, send_timeout: 1, sendfile: 1, + sendfile_max_chunk: 1, server: 1, server_name: 1, + server_name_in_redirect: 1, server_names_hash_bucket_size: 1, + server_names_hash_max_size: 1, server_tokens: 1, 'set': 1, + set_real_ip_from: 1, source_charset: 1, ssi: 1, + ssi_ignore_recycled_buffers: 1, ssi_min_file_chunk: 1, + ssi_silent_errors: 1, ssi_types: 1, ssi_value_length: 1, ssl: 1, + ssl_certificate: 1, ssl_certificate_key: 1, ssl_ciphers: 1, + ssl_client_certificate: 1, ssl_crl: 1, ssl_dhparam: 1, + ssl_prefer_server_ciphers: 1, ssl_protocols: 1, ssl_session_cache: 1, + ssl_session_timeout: 1, ssl_verify_client: 1, ssl_verify_depth: 1, + sub_filter: 1, sub_filter_once: 1, sub_filter_types: 1, tcp_nodelay: 1, + tcp_nopush: 1, timer_resolution: 1, try_files: 1, types: 1, + types_hash_bucket_size: 1, types_hash_max_size: 1, + underscores_in_headers: 1, uninitialized_variable_warn: 1, upstream: 1, + use: 1, user: 1, userid: 1, userid_domain: 1, userid_expires: 1, userid_mark: 1, + userid_name: 1, userid_p3p: 1, userid_path: 1, userid_service: 1, + valid_referers: 1, variables_hash_bucket_size: 1, + variables_hash_max_size: 1, worker_connections: 1, + worker_cpu_affinity: 1, worker_priority: 1, worker_processes: 1, + worker_rlimit_core: 1, worker_rlimit_nofile: 1, + worker_rlimit_sigpending: 1, working_directory: 1, xml_entities: 1, + xslt_stylesheet: 1, xslt_types: 1 + }, + relevance: 0, + contains: [ + hljs.HASH_COMMENT_MODE, + { + begin: '\\s', end: '[;{]', returnBegin: true, returnEnd: true, + lexems: '[a-z/]+', + keywords: { + 'built_in': { + 'on': 1, 'off': 1, 'yes': 1, 'no': 1, 'true': 1, 'false': 1, + 'none': 1, 'blocked': 1, 'debug': 1, 'info': 1, 'notice': 1, + 'warn': 1, 'error': 1, 'crit': 1, 'select': 1, 'permanent': 1, + 'redirect': 1, 'kqueue': 1, 'rtsig': 1, 'epoll': 1, 'poll': 1, + '/dev/poll': 1 + } + }, + relevance: 0, + contains: [ + hljs.HASH_COMMENT_MODE, + { + className: 'string', + begin: '"', end: '"', + contains: [hljs.BACKSLASH_ESCAPE, VAR1, VAR2, VAR3], + relevance: 0 + }, + { + className: 'string', + begin: "'", end: "'", + contains: [hljs.BACKSLASH_ESCAPE, VAR1, VAR2, VAR3], + relevance: 0 + }, + { + className: 'string', + begin: '([a-z]+):/', end: '[;\\s]', returnEnd: true + }, + { + className: 'regexp', + begin: "\\s\\^", end: "\\s|{|;", returnEnd: true, + contains: [hljs.BACKSLASH_ESCAPE, VAR1, VAR2, VAR3] + }, + // regexp locations (~, ~*) + { + className: 'regexp', + begin: "~\\*?\\s+", end: "\\s|{|;", returnEnd: true, + contains: [hljs.BACKSLASH_ESCAPE, VAR1, VAR2, VAR3] + }, + // *.example.com + { + className: 'regexp', + begin: "\\*(\\.[a-z\\-]+)+", + contains: [hljs.BACKSLASH_ESCAPE, VAR1, VAR2, VAR3] + }, + // sub.example.* + { + className: 'regexp', + begin: "([a-z\\-]+\\.)+\\*", + contains: [hljs.BACKSLASH_ESCAPE, VAR1, VAR2, VAR3] + }, + // IP + { + className: 'number', + begin: '\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b' + }, + // units + { + className: 'number', + begin: '\\s\\d+[kKmMgGdshdwy]*\\b', + relevance: 0 + }, + VAR1, VAR2, VAR3 + ] + } + ] + } + ] + } + } +}(); diff --git a/Scratch/js/highlight/languages/objectivec.js b/Scratch/js/highlight/languages/objectivec.js new file mode 100644 index 0000000..a76fe53 --- /dev/null +++ b/Scratch/js/highlight/languages/objectivec.js @@ -0,0 +1,94 @@ +/* +Language: Objective C +Author: Valerii Hiora +*/ + +hljs.LANGUAGES.objectivec = function(){ + var OBJC_KEYWORDS = { + 'keyword': { + 'false': 1, 'int': 1, 'float': 1, 'while': 1, 'private': 1, 'char': 1, + 'catch': 1, 'export': 1, 'sizeof': 2, 'typedef': 2, 'const': 1, + 'struct': 1, 'for': 1, 'union': 1, 'unsigned': 1, 'long': 1, + 'volatile': 2, 'static': 1, 'protected': 1, 'bool': 1, 'mutable': 1, + 'if': 1, 'public': 1, 'do': 1, 'return': 1, 'goto': 1, 'void': 2, + 'enum': 1, 'else': 1, 'break': 1, 'extern': 1, 'true': 1, 'class': 1, + 'asm': 1, 'case': 1, 'short': 1, 'default': 1, 'double': 1, 'throw': 1, + 'register': 1, 'explicit': 1, 'signed': 1, 'typename': 1, 'try': 1, + 'this': 1, 'switch': 1, 'continue': 1, 'wchar_t': 1, 'inline': 1, + 'readonly': 1, 'assign': 1, 'property': 1, 'protocol': 10, 'self': 1, + 'synchronized': 1, 'end': 1, 'synthesize': 50, 'id': 1, 'optional': 1, + 'required': 1, 'implementation': 10, 'nonatomic': 1,'interface': 1, + 'super': 1, 'unichar': 1, 'finally': 2, 'dynamic': 2, 'nil': 1 + }, + 'built_in': { + 'YES': 5, 'NO': 5, 'NULL': 1, 'IBOutlet': 50, 'IBAction': 50, + 'NSString': 50, 'NSDictionary': 50, 'CGRect': 50, 'CGPoint': 50, + 'NSRange': 50, 'release': 1, 'retain': 1, 'autorelease': 50, + 'UIButton': 50, 'UILabel': 50, 'UITextView': 50, 'UIWebView': 50, + 'MKMapView': 50, 'UISegmentedControl': 50, 'NSObject': 50, + 'UITableViewDelegate': 50, 'UITableViewDataSource': 50, 'NSThread': 50, + 'UIActivityIndicator': 50, 'UITabbar': 50, 'UIToolBar': 50, + 'UIBarButtonItem': 50, 'UIImageView': 50, 'NSAutoreleasePool': 50, + 'UITableView': 50, 'BOOL': 1, 'NSInteger': 20, 'CGFloat': 20, + 'NSException': 50, 'NSLog': 50, 'NSMutableString': 50, + 'NSMutableArray': 50, 'NSMutableDictionary': 50, 'NSURL': 50 + } + }; + return { + defaultMode: { + keywords: OBJC_KEYWORDS, + illegal: '' + } + ] + }, + { + className: 'preprocessor', + begin: '#', + end: '$' + }, + { + className: 'class', + begin: 'interface|class|protocol|implementation', + end: '({|$)', + keywords: { + 'interface': 1, + 'class': 1, + 'protocol': 5, + 'implementation': 5 + }, + contains: [{ + className: 'id', + begin: hljs.UNDERSCORE_IDENT_RE + } + ] + } + ] + } + }; +}(); diff --git a/Scratch/js/highlight/languages/parser3.js b/Scratch/js/highlight/languages/parser3.js new file mode 100644 index 0000000..2b5fe43 --- /dev/null +++ b/Scratch/js/highlight/languages/parser3.js @@ -0,0 +1,50 @@ +/* +Language: Parser3 +Requires: xml.js +Author: Oleg Volchkov +*/ + +hljs.LANGUAGES.parser3 = { + defaultMode: { + subLanguage: 'html', + contains: [ + { + className: 'comment', + begin: '^#', end: '$' + }, + { + className: 'comment', + begin: '\\^rem{', end: '}', + relevance: 10, + contains: [ + { + begin: '{', end: '}', + contains: ['self'] + } + ] + }, + { + className: 'preprocessor', + begin: '^@(?:BASE|USE|CLASS|OPTIONS)$', + relevance: 10 + }, + { + className: 'title', + begin: '@[\\w\\-]+\\[[\\w^;\\-]*\\](?:\\[[\\w^;\\-]*\\])?(?:.*)$' + }, + { + className: 'variable', + begin: '\\$\\{?[\\w\\-\\.\\:]+\\}?' + }, + { + className: 'keyword', + begin: '\\^[\\w\\-\\.\\:]+' + }, + { + className: 'number', + begin: '\\^#[0-9a-fA-F]+' + }, + hljs.C_NUMBER_MODE + ] + } +}; diff --git a/Scratch/js/highlight/languages/perl.js b/Scratch/js/highlight/languages/perl.js new file mode 100644 index 0000000..8ead115 --- /dev/null +++ b/Scratch/js/highlight/languages/perl.js @@ -0,0 +1,181 @@ +/* +Language: Perl +Author: Peter Leonov +*/ + +hljs.LANGUAGES.perl = function(){ + var PERL_KEYWORDS = { + 'getpwent': 1, 'getservent': 1, 'quotemeta': 1, 'msgrcv': 1, 'scalar': 1, 'kill': 1, 'dbmclose': 1, 'undef': 1, + 'lc': 1, 'ma': 1, 'syswrite': 1, 'tr': 1, 'send': 1, 'umask': 1, 'sysopen': 1, 'shmwrite': 1, 'vec': 1, 'qx': 1, + 'utime': 1, 'local': 1, 'oct': 1, 'semctl': 1, 'localtime': 1, 'readpipe': 1, 'do': 1, 'return': 1, 'format': 1, + 'read': 1, 'sprintf': 1, 'dbmopen': 1, 'pop': 1, 'getpgrp': 1, 'not': 1, 'getpwnam': 1, 'rewinddir': 1, 'qq': 1, + 'fileno': 1, 'qw': 1, 'endprotoent': 1, 'wait': 1, 'sethostent': 1, 'bless': 1, 's': 1, 'opendir': 1, + 'continue': 1, 'each': 1, 'sleep': 1, 'endgrent': 1, 'shutdown': 1, 'dump': 1, 'chomp': 1, 'connect': 1, + 'getsockname': 1, 'die': 1, 'socketpair': 1, 'close': 1, 'flock': 1, 'exists': 1, 'index': 1, 'shmget': 1, + 'sub': 1, 'for': 1, 'endpwent': 1, 'redo': 1, 'lstat': 1, 'msgctl': 1, 'setpgrp': 1, 'abs': 1, 'exit': 1, + 'select': 1, 'print': 1, 'ref': 1, 'gethostbyaddr': 1, 'unshift': 1, 'fcntl': 1, 'syscall': 1, 'goto': 1, + 'getnetbyaddr': 1, 'join': 1, 'gmtime': 1, 'symlink': 1, 'semget': 1, 'splice': 1, 'x': 1, 'getpeername': 1, + 'recv': 1, 'log': 1, 'setsockopt': 1, 'cos': 1, 'last': 1, 'reverse': 1, 'gethostbyname': 1, 'getgrnam': 1, + 'study': 1, 'formline': 1, 'endhostent': 1, 'times': 1, 'chop': 1, 'length': 1, 'gethostent': 1, 'getnetent': 1, + 'pack': 1, 'getprotoent': 1, 'getservbyname': 1, 'rand': 1, 'mkdir': 1, 'pos': 1, 'chmod': 1, 'y': 1, 'substr': 1, + 'endnetent': 1, 'printf': 1, 'next': 1, 'open': 1, 'msgsnd': 1, 'readdir': 1, 'use': 1, 'unlink': 1, + 'getsockopt': 1, 'getpriority': 1, 'rindex': 1, 'wantarray': 1, 'hex': 1, 'system': 1, 'getservbyport': 1, + 'endservent': 1, 'int': 1, 'chr': 1, 'untie': 1, 'rmdir': 1, 'prototype': 1, 'tell': 1, 'listen': 1, 'fork': 1, + 'shmread': 1, 'ucfirst': 1, 'setprotoent': 1, 'else': 1, 'sysseek': 1, 'link': 1, 'getgrgid': 1, 'shmctl': 1, + 'waitpid': 1, 'unpack': 1, 'getnetbyname': 1, 'reset': 1, 'chdir': 1, 'grep': 1, 'split': 1, 'require': 1, + 'caller': 1, 'lcfirst': 1, 'until': 1, 'warn': 1, 'while': 1, 'values': 1, 'shift': 1, 'telldir': 1, 'getpwuid': 1, + 'my': 1, 'getprotobynumber': 1, 'delete': 1, 'and': 1, 'sort': 1, 'uc': 1, 'defined': 1, 'srand': 1, 'accept': 1, + 'package': 1, 'seekdir': 1, 'getprotobyname': 1, 'semop': 1, 'our': 1, 'rename': 1, 'seek': 1, 'if': 1, 'q': 1, + 'chroot': 1, 'sysread': 1, 'setpwent': 1, 'no': 1, 'crypt': 1, 'getc': 1, 'chown': 1, 'sqrt': 1, 'write': 1, + 'setnetent': 1, 'setpriority': 1, 'foreach': 1, 'tie': 1, 'sin': 1, 'msgget': 1, 'map': 1, 'stat': 1, + 'getlogin': 1, 'unless': 1, 'elsif': 1, 'truncate': 1, 'exec': 1, 'keys': 1, 'glob': 1, 'tied': 1, 'closedir': 1, + 'ioctl': 1, 'socket': 1, 'readlink': 1, 'eval': 1, 'xor': 1, 'readline': 1, 'binmode': 1, 'setservent': 1, + 'eof': 1, 'ord': 1, 'bind': 1, 'alarm': 1, 'pipe': 1, 'atan2': 1, 'getgrent': 1, 'exp': 1, 'time': 1, 'push': 1, + 'setgrent': 1, 'gt': 1, 'lt': 1, 'or': 1, 'ne': 1, 'm': 1 + }; + var SUBST = { + className: 'subst', + begin: '[$@]\\{', end: '\\}', + keywords: PERL_KEYWORDS, + relevance: 10 + }; + var VAR1 = { + className: 'variable', + begin: '\\$\\d' + }; + var VAR2 = { + className: 'variable', + begin: '[\\$\\%\\@\\*](\\^\\w\\b|#\\w+(\\:\\:\\w+)*|[^\\s\\w{]|{\\w+}|\\w+(\\:\\:\\w*)*)' + }; + var STRING_CONTAINS = [hljs.BACKSLASH_ESCAPE, SUBST, VAR1, VAR2]; + var METHOD = { + begin: '->', + contains: [ + {begin: hljs.IDENT_RE}, + {begin: '{', end: '}'} + ] + }; + var COMMENT = { + className: 'comment', + begin: '^(__END__|__DATA__)', end: '\\n$', + relevance: 5 + } + var PERL_DEFAULT_CONTAINS = [ + VAR1, VAR2, + hljs.HASH_COMMENT_MODE, + COMMENT, + METHOD, + { + className: 'string', + begin: 'q[qwxr]?\\s*\\(', end: '\\)', + contains: STRING_CONTAINS, + relevance: 5 + }, + { + className: 'string', + begin: 'q[qwxr]?\\s*\\[', end: '\\]', + contains: STRING_CONTAINS, + relevance: 5 + }, + { + className: 'string', + begin: 'q[qwxr]?\\s*\\{', end: '\\}', + contains: STRING_CONTAINS, + relevance: 5 + }, + { + className: 'string', + begin: 'q[qwxr]?\\s*\\|', end: '\\|', + contains: STRING_CONTAINS, + relevance: 5 + }, + { + className: 'string', + begin: 'q[qwxr]?\\s*\\<', end: '\\>', + contains: STRING_CONTAINS, + relevance: 5 + }, + { + className: 'string', + begin: 'qw\\s+q', end: 'q', + contains: STRING_CONTAINS, + relevance: 5 + }, + { + className: 'string', + begin: '\'', end: '\'', + contains: [hljs.BACKSLASH_ESCAPE], + relevance: 0 + }, + { + className: 'string', + begin: '"', end: '"', + contains: STRING_CONTAINS, + relevance: 0 + }, + { + className: 'string', + begin: '`', end: '`', + contains: [hljs.BACKSLASH_ESCAPE] + }, + { + className: 'string', + begin: '{\\w+}', + relevance: 0 + }, + { + className: 'string', + begin: '\-?\\w+\\s*\\=\\>', + relevance: 0 + }, + { + className: 'number', + begin: '(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b', + relevance: 0 + }, + { // regexp container + begin: '(' + hljs.RE_STARTERS_RE + '|split|return|print|reverse|grep)\\s*', + keywords: {'split': 1, 'return': 1, 'print': 1, 'reverse': 1, 'grep': 1}, + relevance: 0, + contains: [ + hljs.HASH_COMMENT_MODE, + COMMENT, + { + className: 'regexp', + begin: '(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*', + relevance: 10 + }, + { + className: 'regexp', + begin: '(m|qr)?/', end: '/[a-z]*', + contains: [hljs.BACKSLASH_ESCAPE], + relevance: 0 // allows empty "//" which is a common comment delimiter in other languages + } + ] + }, + { + className: 'sub', + begin: '\\bsub\\b', end: '(\\s*\\(.*?\\))?[;{]', + keywords: {'sub':1}, + relevance: 5 + }, + { + className: 'operator', + begin: '-\\w\\b', + relevance: 0 + }, + { + className: 'pod', + begin: '\\=\\w', end: '\\=cut' + } + ]; + SUBST.contains = PERL_DEFAULT_CONTAINS; + METHOD.contains[1].contains = PERL_DEFAULT_CONTAINS; + + return { + defaultMode: { + keywords: PERL_KEYWORDS, + contains: PERL_DEFAULT_CONTAINS + } + }; +}(); diff --git a/Scratch/js/highlight/languages/php.js b/Scratch/js/highlight/languages/php.js new file mode 100644 index 0000000..64d8de1 --- /dev/null +++ b/Scratch/js/highlight/languages/php.js @@ -0,0 +1,77 @@ +/* +Language: PHP +Author: Victor Karamzin +Contributors: Evgeny Stepanischev +*/ + +hljs.LANGUAGES.php = { + case_insensitive: true, + defaultMode: { + keywords: { + 'and': 1, 'include_once': 1, 'list': 1, 'abstract': 1, 'global': 1, + 'private': 1, 'echo': 1, 'interface': 1, 'as': 1, 'static': 1, + 'endswitch': 1, 'array': 1, 'null': 1, 'if': 1, 'endwhile': 1, 'or': 1, + 'const': 1, 'for': 1, 'endforeach': 1, 'self': 1, 'var': 1, 'while': 1, + 'isset': 1, 'public': 1, 'protected': 1, 'exit': 1, 'foreach': 1, + 'throw': 1, 'elseif': 1, 'extends': 1, 'include': 1, '__FILE__': 1, + 'empty': 1, 'require_once': 1, 'function': 1, 'do': 1, 'xor': 1, + 'return': 1, 'implements': 1, 'parent': 1, 'clone': 1, 'use': 1, + '__CLASS__': 1, '__LINE__': 1, 'else': 1, 'break': 1, 'print': 1, + 'eval': 1, 'new': 1, 'catch': 1, '__METHOD__': 1, 'class': 1, 'case': 1, + 'exception': 1, 'php_user_filter': 1, 'default': 1, 'die': 1, + 'require': 1, '__FUNCTION__': 1, 'enddeclare': 1, 'final': 1, 'try': 1, + 'this': 1, 'switch': 1, 'continue': 1, 'endfor': 1, 'endif': 1, + 'declare': 1, 'unset': 1, 'true': 1, 'false': 1, 'namespace': 1, 'trait':1, + 'goto':1, 'instanceof':1, '__DIR__':1, '__NAMESPACE__':1, '__halt_compiler':1 + }, + contains: [ + hljs.C_LINE_COMMENT_MODE, + hljs.HASH_COMMENT_MODE, + { + className: 'comment', + begin: '/\\*', end: '\\*/', + contains: [{ + className: 'phpdoc', + begin: '\\s@[A-Za-z]+' + }] + }, + { + className: 'comment', + excludeBegin: true, + begin: '__halt_compiler[^;]+;', end: '[\\n\\r]$' + }, + hljs.C_NUMBER_MODE, // 0x..., 0..., decimal, float + hljs.BINARY_NUMBER_MODE, // 0b... + hljs.inherit(hljs.APOS_STRING_MODE, {illegal: null}), + hljs.inherit(hljs.QUOTE_STRING_MODE, {illegal: null}), + { + className: 'string', + begin: 'b"', end: '"', + contains: [hljs.BACKSLASH_ESCAPE] + }, + { + className: 'string', + begin: 'b\'', end: '\'', + contains: [hljs.BACKSLASH_ESCAPE] + }, + { + className: 'string', + begin: '<<<[\'"]?\\w+[\'"]?$', end: '^\\w+;', + contains: [hljs.BACKSLASH_ESCAPE] + }, + { + className: 'variable', + begin: '\\$+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*' + }, + { + className: 'preprocessor', + begin: '<\\?php', + relevance: 10 + }, + { + className: 'preprocessor', + begin: '\\?>' + } + ] + } +}; diff --git a/Scratch/js/highlight/languages/profile.js b/Scratch/js/highlight/languages/profile.js new file mode 100644 index 0000000..09715b7 --- /dev/null +++ b/Scratch/js/highlight/languages/profile.js @@ -0,0 +1,49 @@ +/* +Language: Python profile +Description: Python profiler results +Author: Brian Beck +*/ + +hljs.LANGUAGES.profile = { + defaultMode: { + contains: [ + hljs.C_NUMBER_MODE, + { + className: 'builtin', + begin: '{', end: '}$', + excludeBegin: true, excludeEnd: true, + contains: [hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE], + relevance: 0 + }, + { + className: 'filename', + begin: '[a-zA-Z_][\\da-zA-Z_]+\\.[\\da-zA-Z_]{1,3}', end: ':', + excludeEnd: true + }, + { + className: 'header', + begin: '(ncalls|tottime|cumtime)', end: '$', + keywords: {'ncalls': 1, 'tottime': 10, 'cumtime': 10, 'filename': 1}, + relevance: 10 + }, + { + className: 'summary', + begin: 'function calls', end: '$', + contains: [hljs.C_NUMBER_MODE], + relevance: 10 + }, + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE, + { + className: 'function', + begin: '\\(', end: '\\)$', + contains: [{ + className: 'title', + begin: hljs.UNDERSCORE_IDENT_RE, + relevance: 0 + }], + relevance: 0 + } + ] + } +}; diff --git a/Scratch/js/highlight/languages/python.js b/Scratch/js/highlight/languages/python.js new file mode 100644 index 0000000..7617f7a --- /dev/null +++ b/Scratch/js/highlight/languages/python.js @@ -0,0 +1,90 @@ +/* +Language: Python +*/ + +hljs.LANGUAGES.python = function() { + var STRINGS = [ + { + className: 'string', + begin: '(u|b)?r?\'\'\'', end: '\'\'\'', + relevance: 10 + }, + { + className: 'string', + begin: '(u|b)?r?"""', end: '"""', + relevance: 10 + }, + { + className: 'string', + begin: '(u|r|ur)\'', end: '\'', + contains: [hljs.BACKSLASH_ESCAPE], + relevance: 10 + }, + { + className: 'string', + begin: '(u|r|ur)"', end: '"', + contains: [hljs.BACKSLASH_ESCAPE], + relevance: 10 + }, + { + className: 'string', + begin: '(b|br)\'', end: '\'', + contains: [hljs.BACKSLASH_ESCAPE] + }, + { + className: 'string', + begin: '(b|br)"', end: '"', + contains: [hljs.BACKSLASH_ESCAPE] + } + ].concat([ + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE + ]); + var TITLE = { + className: 'title', begin: hljs.UNDERSCORE_IDENT_RE + }; + var PARAMS = { + className: 'params', + begin: '\\(', end: '\\)', + contains: STRINGS.concat([hljs.C_NUMBER_MODE]) + }; + + return { + defaultMode: { + keywords: { + 'keyword': { + 'and': 1, 'elif': 1, 'is': 1, 'global': 1, 'as': 1, 'in': 1, 'if': 1, 'from': 1, 'raise': 1, 'for': 1, + 'except': 1, 'finally': 1, 'print': 1, 'import': 1, 'pass': 1, 'return': 1, 'exec': 1, 'else': 1, + 'break': 1, 'not': 1, 'with': 1, 'class': 1, 'assert': 1, 'yield': 1, 'try': 1, 'while': 1, 'continue': 1, + 'del': 1, 'or': 1, 'def': 1, 'lambda': 1, 'nonlocal': 10 + }, + 'built_in': {'None': 1, 'True': 1, 'False': 1, 'Ellipsis': 1, 'NotImplemented': 1} + }, + illegal: '(|\\?)', + contains: STRINGS.concat([ + hljs.HASH_COMMENT_MODE, + { + className: 'function', + begin: '\\bdef ', end: ':', + illegal: '$', + keywords: {'def': 1}, + contains: [TITLE, PARAMS], + relevance: 10 + }, + { + className: 'class', + begin: '\\bclass ', end: ':', + illegal: '[${]', + keywords: {'class': 1}, + contains: [TITLE, PARAMS], + relevance: 10 + }, + hljs.C_NUMBER_MODE, + { + className: 'decorator', + begin: '@', end: '$' + } + ]) + } + }; +}(); diff --git a/Scratch/js/highlight/languages/renderman.js b/Scratch/js/highlight/languages/renderman.js new file mode 100644 index 0000000..5988ef5 --- /dev/null +++ b/Scratch/js/highlight/languages/renderman.js @@ -0,0 +1,230 @@ +/* +Language: RenderMan +Description: RenderMan Languages RIB and RSL +Author: Konstantin Evdokimenko +Contributors: Shuen-Huei Guan +*/ + +hljs.LANGUAGES.rib = { + defaultMode: { + keywords: { + 'keyword': { + 'ArchiveRecord': 1, + 'AreaLightSource': 1, + 'Atmosphere': 1, + 'Attribute': 1, + 'AttributeBegin': 1, + 'AttributeEnd': 1, + 'Basis': 1, + 'Begin': 1, + 'Blobby': 1, + 'Bound': 1, + 'Clipping': 1, + 'ClippingPlane': 1, + 'Color': 1, + 'ColorSamples': 1, + 'ConcatTransform': 1, + 'Cone': 1, + 'CoordinateSystem': 1, + 'CoordSysTransform': 1, + 'CropWindow': 1, + 'Curves': 1, + 'Cylinder': 1, + 'DepthOfField': 1, + 'Detail': 1, + 'DetailRange': 1, + 'Disk': 1, + 'Displacement': 1, + 'Display': 1, + 'End': 1, + 'ErrorHandler': 1, + 'Exposure': 1, + 'Exterior': 1, + 'Format': 1, + 'FrameAspectRatio': 1, + 'FrameBegin': 1, + 'FrameEnd': 1, + 'GeneralPolygon': 1, + 'GeometricApproximation': 1, + 'Geometry': 1, + 'Hider': 1, + 'Hyperboloid': 1, + 'Identity': 1, + 'Illuminate': 1, + 'Imager': 1, + 'Interior': 1, + 'LightSource': 1, + 'MakeCubeFaceEnvironment': 1, + 'MakeLatLongEnvironment': 1, + 'MakeShadow': 1, + 'MakeTexture': 1, + 'Matte': 1, + 'MotionBegin': 1, + 'MotionEnd': 1, + 'NuPatch': 1, + 'ObjectBegin': 1, + 'ObjectEnd': 1, + 'ObjectInstance': 1, + 'Opacity': 1, + 'Option': 1, + 'Orientation': 1, + 'Paraboloid': 1, + 'Patch': 1, + 'PatchMesh': 1, + 'Perspective': 1, + 'PixelFilter': 1, + 'PixelSamples': 1, + 'PixelVariance': 1, + 'Points': 1, + 'PointsGeneralPolygons': 1, + 'PointsPolygons': 1, + 'Polygon': 1, + 'Procedural': 1, + 'Projection': 1, + 'Quantize': 1, + 'ReadArchive': 1, + 'RelativeDetail': 1, + 'ReverseOrientation': 1, + 'Rotate': 1, + 'Scale': 1, + 'ScreenWindow': 1, + 'ShadingInterpolation': 1, + 'ShadingRate': 1, + 'Shutter': 1, + 'Sides': 1, + 'Skew': 1, + 'SolidBegin': 1, + 'SolidEnd': 1, + 'Sphere': 1, + 'SubdivisionMesh': 1, + 'Surface': 1, + 'TextureCoordinates': 1, + 'Torus': 1, + 'Transform': 1, + 'TransformBegin': 1, + 'TransformEnd': 1, + 'TransformPoints': 1, + 'Translate': 1, + 'TrimCurve': 1, + 'WorldBegin': 1, + 'WorldEnd': 1 + } + }, + illegal: ' +Contributors: Peter Leonov , Vasily Polovnyov , Loren Segal +*/ + +hljs.LANGUAGES.ruby = function(){ + var RUBY_IDENT_RE = '[a-zA-Z_][a-zA-Z0-9_]*(\\!|\\?)?'; + var RUBY_METHOD_RE = '[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?'; + var RUBY_KEYWORDS = { + 'keyword': { + 'and': 1, 'false': 1, 'then': 1, 'defined': 1, 'module': 1, 'in': 1, 'return': 1, 'redo': 1, 'if': 1, + 'BEGIN': 1, 'retry': 1, 'end': 1, 'for': 1, 'true': 1, 'self': 1, 'when': 1, 'next': 1, 'until': 1, 'do': 1, + 'begin': 1, 'unless': 1, 'END': 1, 'rescue': 1, 'nil': 1, 'else': 1, 'break': 1, 'undef': 1, 'not': 1, + 'super': 1, 'class': 1, 'case': 1, 'require': 1, 'yield': 1, 'alias': 1, 'while': 1, 'ensure': 1, + 'elsif': 1, 'or': 1, 'def': 1 + }, + 'keymethods': { + '__id__': 1, '__send__': 1, 'abort': 1, 'abs': 1, 'all?': 1, 'allocate': 1, 'ancestors': 1, 'any?': 1, + 'arity': 1, 'assoc': 1, 'at': 1, 'at_exit': 1, 'autoload': 1, 'autoload?': 1, 'between?': 1, 'binding': 1, + 'binmode': 1, 'block_given?': 1, 'call': 1, 'callcc': 1, 'caller': 1, 'capitalize': 1, 'capitalize!': 1, + 'casecmp': 1, 'catch': 1, 'ceil': 1, 'center': 1, 'chomp': 1, 'chomp!': 1, 'chop': 1, 'chop!': 1, 'chr': 1, + 'class': 1, 'class_eval': 1, 'class_variable_defined?': 1, 'class_variables': 1, 'clear': 1, 'clone': 1, + 'close': 1, 'close_read': 1, 'close_write': 1, 'closed?': 1, 'coerce': 1, 'collect': 1, 'collect!': 1, + 'compact': 1, 'compact!': 1, 'concat': 1, 'const_defined?': 1, 'const_get': 1, 'const_missing': 1, + 'const_set': 1, 'constants': 1, 'count': 1, 'crypt': 1, 'default': 1, 'default_proc': 1, 'delete': 1, + 'delete!': 1, 'delete_at': 1, 'delete_if': 1, 'detect': 1, 'display': 1, 'div': 1, 'divmod': 1, + 'downcase': 1, 'downcase!': 1, 'downto': 1, 'dump': 1, 'dup': 1, 'each': 1, 'each_byte': 1, + 'each_index': 1, 'each_key': 1, 'each_line': 1, 'each_pair': 1, 'each_value': 1, 'each_with_index': 1, + 'empty?': 1, 'entries': 1, 'eof': 1, 'eof?': 1, 'eql?': 1, 'equal?': 1, 'eval': 1, 'exec': 1, 'exit': 1, + 'exit!': 1, 'extend': 1, 'fail': 1, 'fcntl': 1, 'fetch': 1, 'fileno': 1, 'fill': 1, 'find': 1, 'find_all': 1, + 'first': 1, 'flatten': 1, 'flatten!': 1, 'floor': 1, 'flush': 1, 'for_fd': 1, 'foreach': 1, 'fork': 1, + 'format': 1, 'freeze': 1, 'frozen?': 1, 'fsync': 1, 'getc': 1, 'gets': 1, 'global_variables': 1, 'grep': 1, + 'gsub': 1, 'gsub!': 1, 'has_key?': 1, 'has_value?': 1, 'hash': 1, 'hex': 1, 'id': 1, 'include': 1, + 'include?': 1, 'included_modules': 1, 'index': 1, 'indexes': 1, 'indices': 1, 'induced_from': 1, + 'inject': 1, 'insert': 1, 'inspect': 1, 'instance_eval': 1, 'instance_method': 1, 'instance_methods': 1, + 'instance_of?': 1, 'instance_variable_defined?': 1, 'instance_variable_get': 1, 'instance_variable_set': 1, + 'instance_variables': 1, 'integer?': 1, 'intern': 1, 'invert': 1, 'ioctl': 1, 'is_a?': 1, 'isatty': 1, + 'iterator?': 1, 'join': 1, 'key?': 1, 'keys': 1, 'kind_of?': 1, 'lambda': 1, 'last': 1, 'length': 1, + 'lineno': 1, 'ljust': 1, 'load': 1, 'local_variables': 1, 'loop': 1, 'lstrip': 1, 'lstrip!': 1, 'map': 1, + 'map!': 1, 'match': 1, 'max': 1, 'member?': 1, 'merge': 1, 'merge!': 1, 'method': 1, 'method_defined?': 1, + 'method_missing': 1, 'methods': 1, 'min': 1, 'module_eval': 1, 'modulo': 1, 'name': 1, 'nesting': 1, 'new': 1, + 'next': 1, 'next!': 1, 'nil?': 1, 'nitems': 1, 'nonzero?': 1, 'object_id': 1, 'oct': 1, 'open': 1, 'pack': 1, + 'partition': 1, 'pid': 1, 'pipe': 1, 'pop': 1, 'popen': 1, 'pos': 1, 'prec': 1, 'prec_f': 1, 'prec_i': 1, + 'print': 1, 'printf': 1, 'private_class_method': 1, 'private_instance_methods': 1, 'private_method_defined?': 1, + 'private_methods': 1, 'proc': 1, 'protected_instance_methods': 1, 'protected_method_defined?': 1, + 'protected_methods': 1, 'public_class_method': 1, 'public_instance_methods': 1, 'public_method_defined?': 1, + 'public_methods': 1, 'push': 1, 'putc': 1, 'puts': 1, 'quo': 1, 'raise': 1, 'rand': 1, 'rassoc': 1, 'read': 1, + 'read_nonblock': 1, 'readchar': 1, 'readline': 1, 'readlines': 1, 'readpartial': 1, 'rehash': 1, 'reject': 1, + 'reject!': 1, 'remainder': 1, 'reopen': 1, 'replace': 1, 'require': 1, 'respond_to?': 1, 'reverse': 1, + 'reverse!': 1, 'reverse_each': 1, 'rewind': 1, 'rindex': 1, 'rjust': 1, 'round': 1, 'rstrip': 1, 'rstrip!': 1, + 'scan': 1, 'seek': 1, 'select': 1, 'send': 1, 'set_trace_func': 1, 'shift': 1, 'singleton_method_added': 1, + 'singleton_methods': 1, 'size': 1, 'sleep': 1, 'slice': 1, 'slice!': 1, 'sort': 1, 'sort!': 1, 'sort_by': 1, + 'split': 1, 'sprintf': 1, 'squeeze': 1, 'squeeze!': 1, 'srand': 1, 'stat': 1, 'step': 1, 'store': 1, 'strip': 1, + 'strip!': 1, 'sub': 1, 'sub!': 1, 'succ': 1, 'succ!': 1, 'sum': 1, 'superclass': 1, 'swapcase': 1, 'swapcase!': 1, + 'sync': 1, 'syscall': 1, 'sysopen': 1, 'sysread': 1, 'sysseek': 1, 'system': 1, 'syswrite': 1, 'taint': 1, + 'tainted?': 1, 'tell': 1, 'test': 1, 'throw': 1, 'times': 1, 'to_a': 1, 'to_ary': 1, 'to_f': 1, 'to_hash': 1, + 'to_i': 1, 'to_int': 1, 'to_io': 1, 'to_proc': 1, 'to_s': 1, 'to_str': 1, 'to_sym': 1, 'tr': 1, 'tr!': 1, + 'tr_s': 1, 'tr_s!': 1, 'trace_var': 1, 'transpose': 1, 'trap': 1, 'truncate': 1, 'tty?': 1, 'type': 1, + 'ungetc': 1, 'uniq': 1, 'uniq!': 1, 'unpack': 1, 'unshift': 1, 'untaint': 1, 'untrace_var': 1, 'upcase': 1, + 'upcase!': 1, 'update': 1, 'upto': 1, 'value?': 1, 'values': 1, 'values_at': 1, 'warn': 1, 'write': 1, + 'write_nonblock': 1, 'zero?': 1, 'zip': 1 + } + }; + var YARDOCTAG = { + className: 'yardoctag', + begin: '@[A-Za-z]+' + }; + var COMMENTS = [ + { + className: 'comment', + begin: '#', end: '$', + contains: [YARDOCTAG] + }, + { + className: 'comment', + begin: '^\\=begin', end: '^\\=end', + contains: [YARDOCTAG], + relevance: 10 + }, + { + className: 'comment', + begin: '^__END__', end: '\\n$' + } + ]; + var SUBST = { + className: 'subst', + begin: '#\\{', end: '}', + lexems: RUBY_IDENT_RE, + keywords: RUBY_KEYWORDS + }; + var STR_CONTAINS = [hljs.BACKSLASH_ESCAPE, SUBST]; + var STRINGS = [ + { + className: 'string', + begin: '\'', end: '\'', + contains: STR_CONTAINS, + relevance: 0 + }, + { + className: 'string', + begin: '"', end: '"', + contains: STR_CONTAINS, + relevance: 0 + }, + { + className: 'string', + begin: '%[qw]?\\(', end: '\\)', + contains: STR_CONTAINS, + relevance: 10 + }, + { + className: 'string', + begin: '%[qw]?\\[', end: '\\]', + contains: STR_CONTAINS, + relevance: 10 + }, + { + className: 'string', + begin: '%[qw]?{', end: '}', + contains: STR_CONTAINS, + relevance: 10 + }, + { + className: 'string', + begin: '%[qw]?<', end: '>', + contains: STR_CONTAINS, + relevance: 10 + }, + { + className: 'string', + begin: '%[qw]?/', end: '/', + contains: STR_CONTAINS, + relevance: 10 + }, + { + className: 'string', + begin: '%[qw]?%', end: '%', + contains: STR_CONTAINS, + relevance: 10 + }, + { + className: 'string', + begin: '%[qw]?-', end: '-', + contains: STR_CONTAINS, + relevance: 10 + }, + { + className: 'string', + begin: '%[qw]?\\|', end: '\\|', + contains: STR_CONTAINS, + relevance: 10 + } + ]; + var FUNCTION = { + className: 'function', + begin: '\\bdef\\s+', end: ' |$|;', + lexems: RUBY_IDENT_RE, + keywords: RUBY_KEYWORDS, + contains: [ + { + className: 'title', + begin: RUBY_METHOD_RE, + lexems: RUBY_IDENT_RE, + keywords: RUBY_KEYWORDS + }, + { + className: 'params', + begin: '\\(', end: '\\)', + lexems: RUBY_IDENT_RE, + keywords: RUBY_KEYWORDS + } + ].concat(COMMENTS) + }; + var IDENTIFIER = { + className: 'identifier', + begin: RUBY_IDENT_RE, + lexems: RUBY_IDENT_RE, + keywords: RUBY_KEYWORDS, + relevance: 0 + }; + + var RUBY_DEFAULT_CONTAINS = COMMENTS.concat(STRINGS.concat([ + { + className: 'class', + begin: '\\b(class|module)\\b', end: '$|;', + keywords: {'class': 1, 'module': 1}, + contains: [ + { + className: 'title', + begin: '[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?', + relevance: 0 + }, + { + className: 'inheritance', + begin: '<\\s*', + contains: [{ + className: 'parent', + begin: '(' + hljs.IDENT_RE + '::)?' + hljs.IDENT_RE + }] + } + ].concat(COMMENTS) + }, + FUNCTION, + { + className: 'constant', + begin: '(::)?([A-Z]\\w*(::)?)+', + relevance: 0 + }, + { + className: 'symbol', + begin: ':', + contains: STRINGS.concat([IDENTIFIER]), + relevance: 0 + }, + { + className: 'number', + begin: '(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b', + relevance: 0 + }, + { + className: 'number', + begin: '\\?\\w' + }, + { + className: 'variable', + begin: '(\\$\\W)|((\\$|\\@\\@?)(\\w+))' + }, + IDENTIFIER, + { // regexp container + begin: '(' + hljs.RE_STARTERS_RE + ')\\s*', + contains: COMMENTS.concat([ + { + className: 'regexp', + begin: '/', end: '/[a-z]*', + illegal: '\\n', + contains: [hljs.BACKSLASH_ESCAPE] + } + ]), + relevance: 0 + } + ])); + SUBST.contains = RUBY_DEFAULT_CONTAINS; + FUNCTION.contains[1].contains = RUBY_DEFAULT_CONTAINS; + + return { + defaultMode: { + lexems: RUBY_IDENT_RE, + keywords: RUBY_KEYWORDS, + contains: RUBY_DEFAULT_CONTAINS + } + }; +}(); diff --git a/Scratch/js/highlight/languages/rust.js b/Scratch/js/highlight/languages/rust.js new file mode 100644 index 0000000..f97d8f6 --- /dev/null +++ b/Scratch/js/highlight/languages/rust.js @@ -0,0 +1,80 @@ +/* +Language: Rust +Author: Andrey Vlasovskikh +*/ + +hljs.LANGUAGES.rust = function() { + var TITLE = { + className: 'title', + begin: hljs.UNDERSCORE_IDENT_RE + }; + var QUOTE_STRING = { + className: 'string', + begin: '"', end: '"', + contains: [hljs.BACKSLASH_ESCAPE], + relevance: 0 + }; + var NUMBER = { + className: 'number', + begin: '\\b(0[xb][A-Za-z0-9_]+|[0-9_]+(\\.[0-9_]+)?([uif](8|16|32|64)?)?)', + relevance: 0 + }; + var KEYWORDS = { + 'alt': 1, 'any': 1, 'as': 1, 'assert': 1, + 'be': 1, 'bind': 1, 'block': 1, 'bool': 1, 'break': 1, + 'char': 1, 'check': 1, 'claim': 1, 'const': 1, 'cont': 1, + 'dir': 1, 'do': 1, + 'else': 1, 'enum': 1, 'export': 1, + 'f32': 1, 'f64': 1, 'fail': 1, 'false': 1, 'float': 1, 'fn': 10, 'for': 1, + 'i16': 1, 'i32': 1, 'i64': 1, 'i8': 1, 'if': 1, 'iface': 10, 'impl': 10, 'import': 1, 'in': 1, 'int': 1, + 'let': 1, 'log': 1, + 'mod': 1, 'mutable': 1, + 'native': 1, 'note': 1, + 'of': 1, + 'prove': 1, 'pure': 10, + 'resource': 1, 'ret': 1, + 'self': 1, 'str': 1, 'syntax': 1, + 'true': 1, 'type': 1, + 'u16': 1, 'u32': 1, 'u64': 1, 'u8': 1, 'uint': 1, 'unchecked': 1, 'unsafe': 1, 'use': 1, + 'vec': 1, + 'while': 1 + }; + return { + defaultMode: { + keywords: KEYWORDS, + illegal: ' +*/ + +hljs.LANGUAGES.scala = function() { + var ANNOTATION = { + className: 'annotation', begin: '@[A-Za-z]+' + }; + var STRING = { + className: 'string', + begin: 'u?r?"""', end: '"""', + relevance: 10 + }; + return { + defaultMode: { + keywords: { + 'type': 1, 'yield': 1, 'lazy': 1, 'override': 1, 'def': 1, 'with': 1, 'val':1, 'var': 1, 'false': 1, 'true': 1, + 'sealed': 1, 'abstract': 1, 'private': 1, 'trait': 1, 'object': 1, 'null': 1, 'if': 1, 'for': 1, 'while': 1, + 'throw': 1, 'finally': 1, 'protected': 1, 'extends': 1, 'import': 1, 'final': 1, 'return': 1, 'else': 1, + 'break': 1, 'new': 1, 'catch': 1, 'super': 1, 'class': 1, 'case': 1,'package': 1, 'default': 1, 'try': 1, + 'this': 1, 'match': 1, 'continue': 1, 'throws': 1 + }, + contains: [ + { + className: 'javadoc', + begin: '/\\*\\*', end: '\\*/', + contains: [{ + className: 'javadoctag', + begin: '@[A-Za-z]+' + }], + relevance: 10 + }, + hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, + hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, STRING, + { + className: 'class', + begin: '((case )?class |object |trait )', end: '({|$)', + illegal: ':', + keywords: {'case' : 1, 'class': 1, 'trait': 1, 'object': 1}, + contains: [ + { + begin: '(extends|with)', + keywords: {'extends': 1, 'with': 1}, + relevance: 10 + }, + { + className: 'title', + begin: hljs.UNDERSCORE_IDENT_RE + }, + { + className: 'params', + begin: '\\(', end: '\\)', + contains: [ + hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, STRING, + ANNOTATION + ] + } + ] + }, + hljs.C_NUMBER_MODE, + ANNOTATION + ] + } + }; +}(); diff --git a/Scratch/js/highlight/languages/smalltalk.js b/Scratch/js/highlight/languages/smalltalk.js new file mode 100644 index 0000000..4f3c4f2 --- /dev/null +++ b/Scratch/js/highlight/languages/smalltalk.js @@ -0,0 +1,55 @@ +/* +Language: Smalltalk +Author: Vladimir Gubarkov +*/ + +hljs.LANGUAGES.smalltalk = function() { + var VAR_IDENT_RE = '[a-z][a-zA-Z0-9_]*'; + var CHAR = { + className: 'char', + begin: '\\$.{1}' + }; + var SYMBOL = { + className: 'symbol', + begin: '#' + hljs.UNDERSCORE_IDENT_RE + }; + return { + defaultMode: { + keywords: {'self': 1, 'super': 1, 'nil': 1, 'true': 1, 'false': 1, 'thisContext': 1}, // only 6 + contains: [ + { + className: 'comment', + begin: '"', end: '"', + relevance: 0 + }, + hljs.APOS_STRING_MODE, + { + className: 'class', + begin: '\\b[A-Z][A-Za-z0-9_]*', + relevance: 0 + }, + { + className: 'method', + begin: VAR_IDENT_RE + ':' + }, + hljs.C_NUMBER_MODE, + SYMBOL, + CHAR, + { + className: 'localvars', + begin: '\\|\\s*((' + VAR_IDENT_RE + ')\\s*)+\\|' + }, + { + className: 'array', + begin: '\\#\\(', end: '\\)', + contains: [ + hljs.APOS_STRING_MODE, + CHAR, + hljs.C_NUMBER_MODE, + SYMBOL + ] + } + ] + } + }; +}(); diff --git a/Scratch/js/highlight/languages/sql.js b/Scratch/js/highlight/languages/sql.js new file mode 100644 index 0000000..376da7f --- /dev/null +++ b/Scratch/js/highlight/languages/sql.js @@ -0,0 +1,90 @@ +/* +Language: SQL +*/ + +hljs.LANGUAGES.sql = { + case_insensitive: true, + defaultMode: { + illegal: '[^\\s]', + contains: [ + { + className: 'operator', + begin: '(begin|start|commit|rollback|savepoint|lock|alter|create|drop|rename|call|delete|do|handler|insert|load|replace|select|truncate|update|set|show|pragma|grant)\\b', end: ';|$', + keywords: { + 'keyword': { + 'all': 1, 'partial': 1, 'global': 1, 'month': 1, + 'current_timestamp': 1, 'using': 1, 'go': 1, 'revoke': 1, + 'smallint': 1, 'indicator': 1, 'end-exec': 1, 'disconnect': 1, + 'zone': 1, 'with': 1, 'character': 1, 'assertion': 1, 'to': 1, + 'add': 1, 'current_user': 1, 'usage': 1, 'input': 1, 'local': 1, + 'alter': 1, 'match': 1, 'collate': 1, 'real': 1, 'then': 1, + 'rollback': 1, 'get': 1, 'read': 1, 'timestamp': 1, + 'session_user': 1, 'not': 1, 'integer': 1, 'bit': 1, 'unique': 1, + 'day': 1, 'minute': 1, 'desc': 1, 'insert': 1, 'execute': 1, + 'like': 1, 'ilike': 2, 'level': 1, 'decimal': 1, 'drop': 1, + 'continue': 1, 'isolation': 1, 'found': 1, 'where': 1, + 'constraints': 1, 'domain': 1, 'right': 1, 'national': 1, 'some': 1, + 'module': 1, 'transaction': 1, 'relative': 1, 'second': 1, + 'connect': 1, 'escape': 1, 'close': 1, 'system_user': 1, 'for': 1, + 'deferred': 1, 'section': 1, 'cast': 1, 'current': 1, 'sqlstate': 1, + 'allocate': 1, 'intersect': 1, 'deallocate': 1, 'numeric': 1, + 'public': 1, 'preserve': 1, 'full': 1, 'goto': 1, 'initially': 1, + 'asc': 1, 'no': 1, 'key': 1, 'output': 1, 'collation': 1, 'group': 1, + 'by': 1, 'union': 1, 'session': 1, 'both': 1, 'last': 1, + 'language': 1, 'constraint': 1, 'column': 1, 'of': 1, 'space': 1, + 'foreign': 1, 'deferrable': 1, 'prior': 1, 'connection': 1, + 'unknown': 1, 'action': 1, 'commit': 1, 'view': 1, 'or': 1, + 'first': 1, 'into': 1, 'float': 1, 'year': 1, 'primary': 1, + 'cascaded': 1, 'except': 1, 'restrict': 1, 'set': 1, 'references': 1, + 'names': 1, 'table': 1, 'outer': 1, 'open': 1, 'select': 1, + 'size': 1, 'are': 1, 'rows': 1, 'from': 1, 'prepare': 1, + 'distinct': 1, 'leading': 1, 'create': 1, 'only': 1, 'next': 1, + 'inner': 1, 'authorization': 1, 'schema': 1, 'corresponding': 1, + 'option': 1, 'declare': 1, 'precision': 1, 'immediate': 1, 'else': 1, + 'timezone_minute': 1, 'external': 1, 'varying': 1, 'translation': 1, + 'true': 1, 'case': 1, 'exception': 1, 'join': 1, 'hour': 1, + 'default': 1, 'double': 1, 'scroll': 1, 'value': 1, 'cursor': 1, + 'descriptor': 1, 'values': 1, 'dec': 1, 'fetch': 1, 'procedure': 1, + 'delete': 1, 'and': 1, 'false': 1, 'int': 1, 'is': 1, 'describe': 1, + 'char': 1, 'as': 1, 'at': 1, 'in': 1, 'varchar': 1, 'null': 1, + 'trailing': 1, 'any': 1, 'absolute': 1, 'current_time': 1, 'end': 1, + 'grant': 1, 'privileges': 1, 'when': 1, 'cross': 1, 'check': 1, + 'write': 1, 'current_date': 1, 'pad': 1, 'begin': 1, 'temporary': 1, + 'exec': 1, 'time': 1, 'update': 1, 'catalog': 1, 'user': 1, 'sql': 1, + 'date': 1, 'on': 1, 'identity': 1, 'timezone_hour': 1, 'natural': 1, + 'whenever': 1, 'interval': 1, 'work': 1, 'order': 1, 'cascade': 1, + 'diagnostics': 1, 'nchar': 1, 'having': 1, 'left': 1, 'call': 1, + 'do': 1, 'handler': 1, 'load': 1, 'replace': 1, 'truncate': 1, + 'start': 1, 'lock': 1, 'show': 1, 'pragma': 1}, + 'aggregate': {'count': 1, 'sum': 1, 'min': 1, 'max': 1, 'avg': 1} + }, + contains: [ + { + className: 'string', + begin: '\'', end: '\'', + contains: [hljs.BACKSLASH_ESCAPE, {begin: '\'\''}], + relevance: 0 + }, + { + className: 'string', + begin: '"', end: '"', + contains: [hljs.BACKSLASH_ESCAPE, {begin: '""'}], + relevance: 0 + }, + { + className: 'string', + begin: '`', end: '`', + contains: [hljs.BACKSLASH_ESCAPE] + }, + hljs.C_NUMBER_MODE, + {begin: '\\n'} + ] + }, + hljs.C_BLOCK_COMMENT_MODE, + { + className: 'comment', + begin: '--', end: '$' + } + ] + } +}; diff --git a/Scratch/js/highlight/languages/tex.js b/Scratch/js/highlight/languages/tex.js new file mode 100644 index 0000000..36da6ec --- /dev/null +++ b/Scratch/js/highlight/languages/tex.js @@ -0,0 +1,62 @@ +/* +Language: TeX +Author: Vladimir Moskva +Website: http://fulc.ru/ +*/ + +hljs.LANGUAGES.tex = function() { + var COMMAND1 = { + className: 'command', + begin: '\\\\[a-zA-Zа-яА-я]+[\\*]?', + relevance: 10 + }; + var COMMAND2 = { + className: 'command', + begin: '\\\\[^a-zA-Zа-яА-я0-9]', + relevance: 0 + }; + var SPECIAL = { + className: 'special', + begin: '[{}\\[\\]\\&#~]', + relevance: 0 + }; + + return { + defaultMode: { + contains: [ + { // parameter + begin: '\\\\[a-zA-Zа-яА-я]+[\\*]? *= *-?\\d*\\.?\\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?', + returnBegin: true, + contains: [ + COMMAND1, COMMAND2, + { + className: 'number', + begin: ' *=', end: '-?\\d*\\.?\\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?', + excludeBegin: true + } + ], + relevance: 10 + }, + COMMAND1, COMMAND2, + SPECIAL, + { + className: 'formula', + begin: '\\$\\$', end: '\\$\\$', + contains: [COMMAND1, COMMAND2, SPECIAL], + relevance: 0 + }, + { + className: 'formula', + begin: '\\$', end: '\\$', + contains: [COMMAND1, COMMAND2, SPECIAL], + relevance: 0 + }, + { + className: 'comment', + begin: '%', end: '$', + relevance: 0 + } + ] + } + }; +}(); diff --git a/Scratch/js/highlight/languages/vala.js b/Scratch/js/highlight/languages/vala.js new file mode 100644 index 0000000..e730050 --- /dev/null +++ b/Scratch/js/highlight/languages/vala.js @@ -0,0 +1,74 @@ +/* +Language: Vala +Author: Antono Vasiljev +Description: Vala is a new programming language that aims to bring modern programming language features to GNOME developers without imposing any additional runtime requirements and without using a different ABI compared to applications and libraries written in C. +*/ + +hljs.LANGUAGES.vala = { + defaultMode: { + keywords: { + keyword: { + // Value types + 'char': 1, 'uchar': 1, 'unichar': 1, + 'int': 1, 'uint': 1, 'long': 1, 'ulong': 1, + 'short': 1, 'ushort': 1, + 'int8': 1, 'int16': 1, 'int32': 1, 'int64': 1, + 'uint8': 1, 'uint16': 1, 'uint32': 1, 'uint64': 1, + 'float': 1, 'double': 1, 'bool': 1, 'struct': 1, 'enum': 1, + 'string': 1, 'void': 1, + // Reference types + 'weak': 5, 'unowned': 5, 'owned': 5, + // Modifiers + 'async': 5, 'signal': 5, 'static': 1, 'abstract': 1, 'interface': 1, 'override': 1, + // Control Structures + 'while': 1, 'do': 1, 'for': 1, 'foreach': 1, 'else': 1, 'switch': 1, + 'case': 1, 'break': 1, 'default': 1, 'return': 1, 'try': 1, 'catch': 1, + // Visibility + 'public': 1, 'private': 1, 'protected': 1, 'internal': 1, + // Other + 'using': 1, 'new': 1, 'this': 1, 'get': 1, 'set': 1, 'const': 1, + 'stdout': 1, 'stdin': 1, 'stderr': 1, 'var': 1, + // Builtins + 'DBus': 2, 'GLib': 2, 'CCode': 10, 'Gee': 10, 'Object': 1 + }, + literal: { 'false': 1, 'true': 1, 'null': 1 } + }, + contains: [ + { + className: 'class', + begin: '(class |interface |delegate |namespace )', end: '{', + keywords: {'class': 1, 'interface': 1}, + contains: [ + { + begin: '(implements|extends)', + keywords: {'extends': 1, 'implements': 1} + }, + { + className: 'title', + begin: hljs.UNDERSCORE_IDENT_RE + } + ] + }, + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + { + className: 'string', + begin: '"""', end: '"""', + relevance: 5 + }, + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE, + hljs.C_NUMBER_MODE, + { + className: 'preprocessor', + begin: '^#', end: '$', + relevance: 2 + }, + { + className: 'constant', + begin: ' [A-Z_]+ ', + relevance: 0 + } + ] + } +}; diff --git a/Scratch/js/highlight/languages/vbscript.js b/Scratch/js/highlight/languages/vbscript.js new file mode 100644 index 0000000..7f4394b --- /dev/null +++ b/Scratch/js/highlight/languages/vbscript.js @@ -0,0 +1,54 @@ +/* +Language: VBScript +Author: Nikita Ledyaev +Contributors: Michal Gabrukiewicz +*/ + +hljs.LANGUAGES.vbscript = { + case_insensitive: true, + defaultMode: { + keywords: { + 'keyword': { + 'call': 1, 'class': 1, 'const': 1, 'dim': 1, 'do': 1, 'loop': 1, 'erase': 1, 'execute': 1, 'executeglobal': 1, + 'exit': 1, 'for': 1, 'each': 1, 'next': 1, 'function': 1, 'if': 1, 'then': 1, 'else': 1, 'on': 1, 'error': 1, + 'option': 1, 'explicit': 1, 'new': 1, 'private': 1, 'property': 1, 'let': 1, 'get': 1, 'public': 1, + 'randomize': 1, 'redim': 1, 'rem': 1, 'select': 1, 'case': 1, 'set': 1, 'stop': 1, 'sub': 1, 'while': 1, + 'wend': 1, 'with': 1, 'end': 1, 'to': 1, 'elseif': 1, 'is': 1, 'or': 1, 'xor': 1, 'and': 1, 'not': 1, + 'class_initialize': 1, 'class_terminate': 1, 'default': 1, 'preserve': 1, 'in': 1, 'me': 1, 'byval': 1, + 'byref': 1, 'step': 1, 'resume': 1, 'goto': 1 + }, + 'built_in': { + 'lcase': 1, 'month': 1, 'vartype': 1, 'instrrev': 1, 'ubound': 1, 'setlocale': 1, 'getobject': 1, + 'rgb': 1, 'getref': 1, 'string': 1, 'weekdayname': 1, 'rnd': 1, 'dateadd': 1, 'monthname': 1, 'now': 1, + 'day': 1, 'minute': 1, 'isarray': 1, 'cbool': 1, 'round': 1, 'formatcurrency': 1, 'conversions': 1, + 'csng': 1, 'timevalue': 1, 'second': 1, 'year': 1, 'space': 1, 'abs': 1, 'clng': 1, 'timeserial': 1, + 'fixs': 1, 'len': 1, 'asc': 1, 'isempty': 1, 'maths': 1, 'dateserial': 1, 'atn': 1, 'timer': 1, + 'isobject': 1, 'filter': 1, 'weekday': 1, 'datevalue': 1, 'ccur': 1, 'isdate': 1, 'instr': 1, 'datediff': 1, + 'formatdatetime': 1, 'replace': 1, 'isnull': 1, 'right': 1, 'sgn': 1, 'array': 1, 'snumeric': 1, 'log': 1, + 'cdbl': 1, 'hex': 1, 'chr': 1, 'lbound': 1, 'msgbox': 1, 'ucase': 1, 'getlocale': 1, 'cos': 1, 'cdate': 1, + 'cbyte': 1, 'rtrim': 1, 'join': 1, 'hour': 1, 'oct': 1, 'typename': 1, 'trim': 1, 'strcomp': 1, 'int': 1, + 'createobject': 1, 'loadpicture': 1, 'tan': 1, 'formatnumber': 1, 'mid': 1, 'scriptenginebuildversion': 1, + 'scriptengine': 1, 'split': 1, 'scriptengineminorversion': 1, 'cint': 1, 'sin': 1, 'datepart': 1, 'ltrim': 1, + 'sqr': 1, 'scriptenginemajorversion': 1, 'time': 1, 'derived': 1, 'eval': 1, 'date': 1, 'formatpercent': 1, + 'exp': 1, 'inputbox': 1, 'left': 1, 'ascw': 1, 'chrw': 1, 'regexp': 1, 'server': 1, 'response': 1, + 'request': 1, 'cstr': 1, 'err': 1 + }, + 'literal': {'true': 1, 'false': 1, 'null': 1, 'nothing': 1, 'empty': 1} + }, + illegal: '//', + contains: [ + { // can't use standard QUOTE_STRING_MODE since it's compiled with its own escape and doesn't use the local one + className: 'string', + begin: '"', end: '"', + illegal: '\\n', + contains: [{begin: '""'}], + relevance: 0 + }, + { + className: 'comment', + begin: '\'', end: '$' + }, + hljs.C_NUMBER_MODE + ] + } +}; diff --git a/Scratch/js/highlight/languages/vhdl.js b/Scratch/js/highlight/languages/vhdl.js new file mode 100644 index 0000000..9f5c359 --- /dev/null +++ b/Scratch/js/highlight/languages/vhdl.js @@ -0,0 +1,46 @@ +/* +Language: VHDL +Description: VHDL is a hardware description language used in electronic design automation to describe digital and mixed-signal systems. +Author: Igor Kalnitsky +Website: http://kalnitsky.org.ua/ +*/ + +hljs.LANGUAGES.vhdl = { + case_insensitive: true, + defaultMode: { + keywords: { + 'keyword': { + 'abs': 1, 'access': 1, 'after': 1, 'alias': 1, 'all': 1, 'and': 1, 'architecture': 2, 'array': 1, 'assert': 1, + 'attribute': 1, 'begin': 1, 'block': 1, 'body': 1, 'buffer': 1, 'bus': 1, 'case': 1, 'component': 2, + 'configuration': 1, 'constant': 1, 'disconnect': 2, 'downto': 2, 'else': 1, 'elsif': 1, 'end': 1, 'entity': 2, + 'exit': 1, 'file': 1, 'for': 1, 'function': 1, 'generate': 2, 'generic': 2, 'group': 1, 'guarded': 2, 'if': 0, + 'impure': 2, 'in': 1, 'inertial': 1, 'inout': 1, 'is': 1, 'label': 1, 'library': 1, 'linkage': 1, 'literal': 1, + 'loop': 1, 'map': 1, 'mod': 1, 'nand': 1, 'new': 1, 'next': 1, 'nor': 1, 'not': 1, 'null': 1, 'of': 1, 'on': 1, + 'open': 1, 'or': 1, 'others': 1, 'out': 1, 'package': 1, 'port': 2, 'postponed': 1, 'procedure': 1, + 'process': 1, 'pure': 2, 'range': 1, 'record': 1, 'register': 1, 'reject': 1, 'return': 1, 'rol': 1, 'ror': 1, + 'select': 1, 'severity': 1, 'signal': 1, 'shared': 1, 'sla': 1, 'sli': 1, 'sra': 1, 'srl': 1, 'subtype': 2, + 'then': 1, 'to': 1, 'transport': 1, 'type': 1, 'units': 1, 'until': 1, 'use': 1, 'variable': 1, 'wait': 1, + 'when': 1, 'while': 1, 'with': 1, 'xnor': 1, 'xor': 1 + }, + 'type': { + 'boolean': 1, 'bit': 1, 'character': 1, 'severity_level': 2, 'integer': 1, 'time': 1, 'delay_length': 2, + 'natural': 1, 'positive': 1, 'string': 1, 'bit_vector': 2, 'file_open_kind': 2, 'file_open_status': 2, + 'std_ulogic': 2, 'std_ulogic_vector': 2, 'std_logic': 2, 'std_logic_vector': 2 + } + }, + illegal: '{', + contains: [ + { + className: 'comment', + begin: '--', end: '$' + }, + hljs.QUOTE_STRING_MODE, + hljs.C_NUMBER_MODE, + { + className: 'literal', + begin: '\'(U|X|0|1|Z|W|L|H|-)', end: '\'', + contains: [hljs.BACKSLASH_ESCAPE] + } + ] + } +}; diff --git a/Scratch/js/highlight/languages/xml.js b/Scratch/js/highlight/languages/xml.js new file mode 100644 index 0000000..24f377e --- /dev/null +++ b/Scratch/js/highlight/languages/xml.js @@ -0,0 +1,110 @@ +/* +Language: HTML, XML +*/ + +hljs.LANGUAGES.xml = function(){ + var XML_IDENT_RE = '[A-Za-z0-9\\._:-]+'; + var TAG_INTERNALS = { + endsWithParent: true, + contains: [ + { + className: 'attribute', + begin: XML_IDENT_RE, + relevance: 0 + }, + { + begin: '="', returnBegin: true, end: '"', + contains: [{ + className: 'value', + begin: '"', endsWithParent: true + }] + }, + { + begin: '=\'', returnBegin: true, end: '\'', + contains: [{ + className: 'value', + begin: '\'', endsWithParent: true + }] + }, + { + begin: '=', + contains: [{ + className: 'value', + begin: '[^\\s/>]+' + }] + } + ] + }; + return { + case_insensitive: true, + defaultMode: { + contains: [ + { + className: 'pi', + begin: '<\\?', end: '\\?>', + relevance: 10 + }, + { + className: 'doctype', + begin: '', + relevance: 10, + contains: [{begin: '\\[', end: '\\]'}] + }, + { + className: 'comment', + begin: '', + relevance: 10 + }, + { + className: 'cdata', + begin: '<\\!\\[CDATA\\[', end: '\\]\\]>', + relevance: 10 + }, + { + className: 'tag', + /* + The lookahead pattern (?=...) ensures that 'begin' only matches + '|$)', end: '>', + keywords: {'title': {'style': 1}}, + contains: [TAG_INTERNALS], + starts: { + className: 'css', + end: '', returnEnd: true, + subLanguage: 'css' + } + }, + { + className: 'tag', + // See the comment in the + + + + + + + +

    This is a demo/test page showing all languages supported by highlight.js. +Most snippets do not contain working code :-). + +

    +

    Styles

    +
    + +

    Automatically detected languages

    + +

    ...

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Python + +
    +@requires_authorization
    +def somefunc(param1='', param2=0):
    +    r'''A docstring'''
    +    if param1 > param2: # interesting
    +        print 'Gre\'ater'
    +    return (param2 - param1 + 1) or None
    +
    +class SomeClass:
    pass +
    + +
    Python's profiler output + +
           261917242 function calls in 686.251 CPU seconds
    +
    +       ncalls  tottime  filename:lineno(function)
    +       152824  513.894  {method 'sort' of 'list' objects}
    +    129590630   83.894  rrule.py:842(__cmp__)
    +    129590630   82.439  {cmp}
    +       153900    1.296  rrule.py:399(_iter)
    +304393/151570    0.963  rrule.py:102(_iter_cached)
    +
    + +
    Ruby + +
    class A < B; def self.create(object = User) object end end
    +class Zebra; def inspect; "X#{2 + self.object_id}" end end
    +
    +module ABC::DEF
    +  include Comparable
    +
    +  # @param test
    +  # @return [String] nothing
    +  def foo(test)
    +    Thread.new do |blockvar|
    +      ABC::DEF.reverse(:a_symbol, :'a symbol' + 'test' + test)
    +    end.join
    +  end
    +
    +  def [](index) self[index] end
    +  def ==(other) other == self end
    +end
    +
    +anIdentifier = an_identifier
    +Constant = 1
    +
    + +
    Perl + +
    # loads object
    +sub load
    +{
    +  my $flds = $c->db_load($id,@_) || do {
    +    Carp::carp "Can`t load (class: $c, id: $id): '$!'"; return undef
    +  };
    +  my $o = $c->_perl_new();
    +  $id12 = $id / 24 / 3600;
    +  $o->{'ID'} = $id12 + 123;
    +  $o->{'PAPA'} = $flds->{'PAPA'};
    +  #$o->{'SHCUT'} = $flds->{'SHCUT'};
    +  my $p = $o->props;
    +  my $vt;
    +  $string =~ m/^sought_text$/;
    +  $items = split //, 'abc';
    +  for my $key (keys %$p)
    +  {
    +    if(${$vt.'::property'}) {
    +      $o->{$key . '_real'} = $flds->{$key};
    +      tie $o->{$key}, 'CMSBuilder::Property', $o, $key;
    +    } else {
    +      $o->{$key} = $flds->{$key};
    +      $o->kill();
    +    }
    +  }
    +  $o->save if delete $o->{'_save_after_load'};
    +  return $o;
    +}
    +
    +foreach my $num (0..$#array) {
    +  # something
    +}
    +
    + +
    PHP + +
    require_once 'Zend.php';
    +require_once 'Zend/Uri/Exception.php';
    +require_once 'Zend/Uri/Http.php';
    +require_once 'Zend/Uri/Mailto.php';
    +
    +abstract class Zend_Uri
    +{
    +
    +  /**
    +   * Return a string representation of this URI.
    +   *
    +   * @see     getUri()
    +   * @return  string
    +   */
    +  public function __toString()
    +  {
    +      return $this->getUri();
    +  }
    +
    +  static public function factory($uri = 'http')
    +  {
    +      $uri = explode(':', $uri, 2);
    +      $scheme = strtolower($uri[0]);
    +      $schemeSpecific = isset($uri[1]) ? $uri[1] : '';
    +      $desc = 'Multi
    +line description';
    +
    +      // Security check: $scheme is used to load a class file,
    +      // so only alphanumerics are allowed.
    +      if (!ctype_alnum($scheme)) {
    +          throw new Zend_Uri_Exception('Illegal scheme');
    +      }
    +  }
    +}
    +
    +__halt_compiler () ; datahere
    +datahere
    +datahere */
    +datahere
    +
    +
    + +
    Scala + +
    object abstractTypes extends Application {
    +  abstract class SeqBuffer {
    +    type T; val element: Seq[T]; def length = element.length
    +  }
    +}
    +
    +/** Turn command line arguments to uppercase */
    +object Main {
    +  def main(args: Array[String]) {
    +    val res = for (a <- args) yield a.toUpperCase
    +    println("Arguments: " + res.toString)
    +  }
    +}
    +
    +/** Maps are easy to use in Scala. */
    +object Maps {
    +  val colors = Map("red" -> 0xFF0000,
    +                   "turquoise" -> 0x00FFFF,
    +                   "black" -> 0x000000,
    +                   "orange" -> 0xFF8040,
    +                   "brown" -> 0x804000)
    +  def main(args: Array[String]) {
    +    for (name <- args) println(
    +      colors.get(name) match {
    +        case Some(code) =>
    +          name + " has code: " + code
    +        case None =>
    +          "Unknown color: " + name
    +      }
    +    )
    +  }
    +}
    +
    + +
    Go + +
    package main
    +
    +import (
    +    "fmt"
    +    "rand"
    +    "os"
    +)
    +
    +const (
    +    Sunday = iota
    +    Partyday
    +    numberOfDays  // this constant is not exported
    +)
    +
    +type Foo interface {
    +    FooFunc(int, float32) (complex128, []int)
    +}
    +
    +// simple comment
    +type Bar struct {
    +    os.File /* multi
    +    line
    +    comment */
    +
    +    PublicData chan int
    +}
    +
    +func main() {
    +    ch := make(chan int)
    +    ch <- 1
    +    x, ok := <- ch
    +    ok = true
    +    x = nil
    +    float_var := 1.0e10
    +    defer fmt.Println('\'')
    +    defer fmt.Println(`exitting now\`)
    +    var fv1 float64 = 0.75
    +    go println(len("hello world!"))
    +    return
    +}
    +
    +
    + +
    XML + +
    <?xml version="1.0"?>
    +<response value="ok" xml:lang="en">
    +  <text>Ok</text>
    +  <comment html_allowed="true"/>
    +  <ns1:description><![CDATA[
    +  CDATA is <not> magical.
    +  ]]></ns1:description>
    +  <a></a> <a/>
    +</response>
    +
    + +
    HTML (with inline css and javascript) + +
    <!DOCTYPE html5>
    +<head>
    +  <title>Title</title>
    +
    +  <style>
    +    body {
    +      width: 500px;
    +    }
    +  </style>
    +
    +  <script type="application/javascript">
    +    function someFunction() {
    +      return true;
    +    }
    +  </script>
    +
    +<body>
    +  <p class="something" id='12'>Something</p>
    +  <p class=something>Something</p>
    +  <!-- comment -->
    +  <p class>Something</p>
    +  <p class="something" title="p">Something</p>
    +</body>
    +
    + +
    Markdown + +
    +# hello world
    +
    +you can write text [with links](http://example.com).
    +
    +* one _thing_ has *em*phasis
    +* two __things__ are **bold**
    +
    +---
    +
    +hello world
    +===========
    +
    +<this_is inline="xml"></this_is>
    +
    +> markdown is so cool
    +
    +    so are code segments
    +
    +1. one thing (yeah!)
    +2. two thing `i can write code`, and `more` wipee!
    +
    + +
    Django templates + +
    {% if articles|length %}
    +{% for article in articles %}
    +
    +{# Striped table #}
    +<tr class="{% cycle odd,even %}">
    +  <td>{{ article|default:"Hi... "|escape }}</td>
    +  <td {% if article.today %}class="today"{% endif %}>{{ article.date|date:"d.m.Y" }}</td>
    +</tr>
    +
    +{% endfor %}
    +{% endif %}
    +
    +{% comment %}
    +Comments may be long and
    +multiline.
    +{% endcomment %}
    +
    + +
    CSS + +
    body,
    +html {
    +  font: Tahoma, Arial, san-serif;
    +  background: url('hatch.png');
    +}
    +
    +@import url('print.css');
    +
    +@media screen and (-webkit-min-device-pixel-ratio: 0) {
    +  body:first-of-type pre::after {
    +    content: 'highlight: ' attr(class);
    +  }
    +}
    +
    +@page:right {
    + margin: 1cm 2cm 1.3cm 4cm;
    +}
    +
    +@font-face {
    +	font-family: Chunkfive;
    +	src: url('Chunkfive.otf');
    +}
    +
    +#content {
    +  width: /* wide enough */ 100% /* 400px */;
    +  height: 100%
    +}
    +
    +p[lang=ru] {
    +  color: #F0F0F0; background: white !important;
    +}
    +
    + +
    JavaScript + +
    function $initHighlight(block) {
    +  if (block.className.search(/\bno\-highlight\b/) != -1)
    +    return false;
    +  try {
    +    blockText(block);
    +  } catch (e) {
    +    if (e == 'Complex markup')
    +      return;
    +  }//try
    +  var classes = block.className.split(/\s+/);
    +  for (var i = 0 / 2; i < classes.length; i++) { // "0 / 2" should not be parsed as regexp start
    +    if (LANGUAGES[classes[i]]) {
    +      highlightLanguage(block, classes[i]);
    +      return;
    +    }//if
    +  }//for
    +  highlightAuto(block);
    +}//initHighlight
    + +
    CoffeeScript + +
    grade = (student) ->
    +  if student.excellentWork
    +    "A+"
    +  else if student.okayStuff
    +    if student.triedHard then "B" else "B-"
    +  else
    +    "C"
    +
    +eldest = if 24 > 21 then "Liz" else "Ike"
    +
    +square = (x) -> x * x
    +
    +two = -> 2
    +
    +math =
    +  root:   Math.sqrt
    +  square: square
    +  cube:   (x) -> x * square x
    +
    +race = (winner, runners...) ->
    +  print winner, runners
    +
    +hi = `function() {
    +  return [document.title, "Hello JavaScript"].join(": ");
    +}`
    +
    +substr = "JavaScript numbers test #{ 010 / 0xf }"
    +
    +heredoc = """
    +CoffeeScript numbers test #{ 010 / 0b10 }
    +"""
    +
    +###
    +CoffeeScript Compiler v1.2.0
    +Released under the MIT License
    +###
    +
    +OPERATOR = /// ^ (
    +?: [-=]>             # function
    + | [-+*/%<>&|^!?=]=  # compound assign / compare
    + | >>>=?             # zero-fill right shift
    + | ([-+:])\1         # doubles
    + | ([&|<>])\2=?      # logic / shift
    + | \?\.              # soak access
    + | \.{2,3}           # range or splat
    +) ///
    + +
    ActionScript + +
    package org.example.dummy {
    +    import org.dummy.*;
    +
    +    /*define package inline interface*/
    +    public interface IFooBarzable {
    +        public function foo(... pairs):Array;
    +    }
    +
    +    public class FooBar implements IFooBarzable {
    +        static private var cnt:uint = 0;
    +
    +        private var bar:String;
    +
    +        //constructor
    +        public function TestBar(bar:String):void {
    +            bar = bar;
    +
    +            ++cnt;
    +        }
    +
    +        public function foo(... pairs):Array {
    +            pairs.push(bar);
    +
    +            return pairs;
    +        }
    +
    +        protected function includeTestFile():void {
    +            include "Test.as";
    +        }
    +    }
    +}
    + +
    VBScript + +
    ' creating configuration storage and initializing with default values
    +Set cfg = CreateObject("Scripting.Dictionary")
    +
    +' reading ini file
    +for i = 0 to ubound(ini_strings)
    +    s = trim(ini_strings(i))
    +
    +    ' skipping empty strings and comments
    +    if mid(s, 1, 1) <> "#" and len(s) > 0 then
    +      ' obtaining key and value
    +      parts = split(s, "=", -1, 1)
    +
    +      if ubound(parts)+1 = 2 then
    +        parts(0) = trim(parts(0))
    +        parts(1) = trim(parts(1))
    +
    +        ' reading configuration and filenames
    +        select case lcase(parts(0))
    +          case "uncompressed""_postfix" cfg.item("uncompressed""_postfix") = parts(1)
    +          case "f"
    +                    options = split(parts(1), "|", -1, 1)
    +                    if ubound(options)+1 = 2 then
    +                      ' 0: filename,  1: options
    +                      ff.add trim(options(0)), trim(options(1))
    +                    end if
    +        end select
    +      end if
    +    end if
    +next
    + +
    Lua + +
    --[[
    +Simple signal/slot implementation
    +]]
    +local signal_mt = {
    +    __index = {
    +        register = table.insert
    +    }
    +}
    +function signal_mt.__index:emit(... --[[ Comment in params ]])
    +    for _, slot in ipairs(self) do
    +        slot(self, ...)
    +    end
    +end
    +local function create_signal()
    +    return setmetatable({}, signal_mt)
    +end
    +
    +-- Signal test
    +local signal = create_signal()
    +signal:register(function (signal, ...)
    +    print(...)
    +end)
    +signal:emit('Answer to Life, the Universe, and Everything:', 42)
    +
    +--[==[ [=[ [[
    +Nested ]]
    +multi-line ]=]
    +comment ]==]
    +[==[ Nested
    +[=[ multi-line
    +[[ string
    +]] ]=] ]==]
    +
    + +
    Delphi + +
    TList=Class(TObject)
    +Private
    +  Some: String;
    +Public
    +  Procedure Inside; // Suxx
    +End;{TList}
    +
    +Procedure CopyFile(InFileName,var OutFileName:String);
    +Const
    +  BufSize=4096; (* Huh? *)
    +Var
    +  InFile,OutFile:TStream;
    +  Buffer:Array[1..BufSize] Of Byte;
    +  ReadBufSize:Integer;
    +Begin
    +  InFile:=Nil;
    +  OutFile:=Nil;
    +  Try
    +    InFile:=TFileStream.Create(InFileName,fmOpenRead);
    +    OutFile:=TFileStream.Create(OutFileName,fmCreate);
    +    Repeat
    +      ReadBufSize:=InFile.Read(Buffer,BufSize);
    +      OutFile.Write(Buffer,ReadBufSize);
    +    Until ReadBufSize<>BufSize;
    +    Log('File '''+InFileName+''' copied'#13#10);
    +  Finally
    +    InFile.Free;
    +    OutFile.Free;
    +  End;{Try}
    +End;{CopyFile}
    +
    + +
    Java + +
    package l2f.gameserver.model;
    +
    +import java.util.ArrayList;
    +
    +/**
    + * Mother class of all character objects of the world (PC, NPC...)<BR><BR>
    + *
    + */
    +public abstract class L2Character extends L2Object
    +{
    +  protected static final Logger _log = Logger.getLogger(L2Character.class.getName());
    +
    +  public static final Short ABNORMAL_EFFECT_BLEEDING = 0x0001; // not sure
    +  public static final Short ABNORMAL_EFFECT_POISON = 0x0002;
    +
    +  public void detachAI() {
    +    _ai = null;
    +    //jbf = null;
    +    if (1 > 5) {
    +      return;
    +    }
    +  }
    +
    +  public void moveTo(int x, int y, int z) {
    +    moveTo(x, y, z, 0);
    +  }
    +
    +  /** Task of AI notification */
    +  @SuppressWarnings( { "nls", "unqualified-field-access", "boxing" })
    +  public class NotifyAITask implements Runnable {
    +    private final CtrlEvent _evt;
    +
    +    public void run() {
    +      try {
    +        getAI().notifyEvent(_evt, null, null);
    +      } catch (Throwable t) {
    +        _log.warning("Exception " + t);
    +        t.printStackTrace();
    +      }
    +    }
    +  }
    +
    +}
    +
    + +
    C++ + +
    #include <iostream>
    +
    +int main(int argc, char *argv[]) {
    +
    +  /* An annoying "Hello World" example */
    +  for (auto i = 0; i < 0xFFFF; i++)
    +    cout << "Hello, World!" << endl;
    +
    +  char c = '\n';
    +  unordered_map <string, vector<string> > m;
    +  m["key"] = "\\\\"; // this is an error
    +
    +  return -2e3 + 12l;
    +}
    +
    + +
    Objective C + +
    +
    +#import <UIKit/UIKit.h>
    +#import "Dependency.h"
    +
    +@protocol WorldDataSource
    +@optional
    +- (NSString*)worldName;
    +@required
    +- (BOOL)allowsToLive;
    +@end
    +
    +@interface Test : NSObject <HelloDelegate, WorldDataSource> {
    +	NSString *_greeting;
    +}
    +
    +@property (nonatomic, readonly) NSString *greeting;
    +- (IBAction) show;
    +@end
    +
    +@implementation Test
    +
    +@synthesize test=_test;
    +
    ++ (id) test {
    +	return [self testWithGreeting:@"Hello, world!\nFoo bar!"];
    +}
    +
    ++ (id) testWithGreeting:(NSString*)greeting {
    +	return [[[self alloc] initWithGreeting:greeting] autorelease];
    +}
    +
    +- (id) initWithGreeting:(NSString*)greeting {
    +	if ( (self = [super init]) ) {
    +		_greeting = [greeting retain];
    +	}
    +	return self;
    +}
    +
    +- (void) dealloc {
    +	[_greeting release];
    +	[super dealloc];
    +}
    +
    +@end
    +
    +
    + +
    Vala + +
    using DBus;
    +
    +namespace Test {
    +  class Foo : Object {
    +    public signal void some_event ();   // definition of the signal
    +    public void method () {
    +      some_event ();                    // emitting the signal (callbacks get invoked)
    +    }
    +  }
    +}
    +
    +/* defining a class */
    +class Track : GLib.Object {              /* subclassing 'GLib.Object' */
    +	public double mass;                  /* a public field */
    +	public double name { get; set; }     /* a public property */
    +	private bool terminated = false;     /* a private field */
    +	public void terminate() {            /* a public method */
    +	  terminated = true;
    +	}
    +}
    +
    +const ALL_UPPER_CASE = "you should follow this convention";
    +
    +var t = new Track();      // same as: Track t = new Track();
    +var s = "hello";          // same as: string s = "hello";
    +var l = new List<int>();       // same as: List<int> l = new List<int>();
    +var i = 10;               // same as: int i = 10;
    +
    +
    +#if (ololo)
    +Regex regex = /foo/;
    +#endif
    +
    +/*
    + * Entry point can be outside class
    + */
    +void main () {
    +  var long_string = """
    +    Example of "verbatim string".
    +    Same as in @"string" in C#
    +  """
    +  var foo = new Foo ();
    +  foo.some_event.connect (callback_a);      // connecting the callback functions
    +  foo.some_event.connect (callback_b);
    +  foo.method ();
    +}
    +
    + +
    C# + +
    using System;
    +
    +#pragma warning disable 414, 3021
    +
    +public class Program
    +{
    +    /// <summary>The entry point to the program.</summary>
    +    /// <remarks>
    +    /// Using the Visual Studio style, the tags in this comment
    +    /// should be grey, but this text should be green.
    +    /// This comment should be green on the inside:
    +    /// <!-- I'm green! -->
    +    /// </remarks>
    +    public static int Main(string[] args)
    +    {
    +        Console.WriteLine("Hello, World!");
    +        string s = @"This
    +""string""
    +spans
    +multiple
    +lines!";
    +        return 0;
    +    }
    +}
    +
    + +
    RenderMan RSL + +
    #define TEST_DEFINE 3.14
    +/*	plastic surface shader
    + *
    + * 	Pixie is:
    + * 	(c) Copyright 1999-2003 Okan Arikan. All rights reserved.
    + */
    +
    +surface plastic (float Ka = 1, Kd = 0.5, Ks = 0.5, roughness = 0.1;
    +                 color specularcolor = 1;) {
    +  normal Nf = faceforward (normalize(N),I);
    +  Ci = Cs * (Ka*ambient() + Kd*diffuse(Nf)) + specularcolor * Ks *
    +       specular(Nf,-normalize(I),roughness);
    +  Oi = Os;
    +  Ci *= Oi;
    +}
    +
    + +
    RenderMan RIB + +
    FrameBegin 0
    +Display "Scene" "framebuffer" "rgb"
    +Option "searchpath" "shader" "+&:/home/kew"
    +Option "trace" "int maxdepth" [4]
    +Attribute "visibility" "trace" [1]
    +Attribute "irradiance" "maxerror" [0.1]
    +Attribute "visibility" "transmission" "opaque"
    +Format 640 480 1.0
    +ShadingRate 2
    +PixelFilter "catmull-rom" 1 1
    +PixelSamples 4 4
    +Projection "perspective" "fov" 49.5502811377
    +Scale 1 1 -1
    +
    +WorldBegin
    +
    +ReadArchive "Lamp.002_Light/instance.rib"
    +Surface "plastic"
    +ReadArchive "Cube.004_Mesh/instance.rib"
    +# ReadArchive "Sphere.010_Mesh/instance.rib"
    +# ReadArchive "Sphere.009_Mesh/instance.rib"
    +ReadArchive "Sphere.006_Mesh/instance.rib"
    +
    +WorldEnd
    +FrameEnd
    +
    + +
    MEL (Maya Embedded Language) + +
    proc string[] getSelectedLights()
    +
    +{
    +  string $selectedLights[];
    +
    +  string $select[] = `ls -sl -dag -leaf`;
    +
    +  for ( $shape in $select )
    +  {
    +    // Determine if this is a light.
    +    //
    +    string $class[] = getClassification( `nodeType $shape` );
    +
    +
    +    if ( ( `size $class` ) > 0 && ( "light" == $class[0] ) )
    +    {
    +      $selectedLights[ `size $selectedLights` ] = $shape;
    +    }
    +  }
    +
    +  // Result is an array of all lights included in
    +
    +  // current selection list.
    +  return $selectedLights;
    +}
    +
    + +
    SQL + +
    BEGIN;
    +CREATE TABLE "cicero_topic" (
    +    "id" serial NOT NULL PRIMARY KEY,
    +    "forum_id" integer NOT NULL,
    +    "subject" varchar(255) NOT NULL,
    +    "created" timestamp with time zone NOT NULL
    +);
    +ALTER TABLE "cicero_topic"
    +ADD CONSTRAINT forum_id_refs_id_4be56999
    +FOREIGN KEY ("forum_id")
    +REFERENCES "cicero_forum" ("id")
    +DEFERRABLE INITIALLY DEFERRED;
    +
    +-- Initials
    +insert into "cicero_forum"
    +  ("slug", "name", "group", "ordering")
    +values
    +  ('test', 'Forum for te''sting', 'Test', 0);
    +
    +-- Test
    +select count(*) from cicero_forum;
    +
    +COMMIT;
    +
    + +
    SmallTalk + +
    Object>>method: num
    +    "comment 123"
    +    | var1 var2 |
    +    (1 to: num) do: [:i | |var| ^i].
    +    Klass with: var1.
    +    Klass new.
    +    arr := #('123' 123.345 #hello Transcript var $@).
    +    arr := #().
    +    var2 = arr at: 3.
    +    ^ self abc
    +
    +heapExample
    +    "HeapTest new heapExample"
    +    "Multiline
    +    decription"
    +    | n rnd array time sorted |
    +    n := 5000.
    +    "# of elements to sort"
    +    rnd := Random new.
    +    array := (1 to: n)
    +                collect: [:i | rnd next].
    +    "First, the heap version"
    +    time := Time
    +                millisecondsToRun: [sorted := Heap withAll: array.
    +    1
    +        to: n
    +        do: [:i |
    +            sorted removeFirst.
    +            sorted add: rnd next]].
    +    Transcript cr; show: 'Time for Heap: ' , time printString , ' msecs'.
    +    "The quicksort version"
    +    time := Time
    +                millisecondsToRun: [sorted := SortedCollection withAll: array.
    +    1
    +        to: n
    +        do: [:i |
    +            sorted removeFirst.
    +            sorted add: rnd next]].
    +    Transcript cr; show: 'Time for SortedCollection: ' , time printString , ' msecs'
    +
    + +
    Lisp + +
    (defun prompt-for-cd ()
    +   "Prompts
    +    for CD"
    +   (prompt-read "Title" 1.53 1 2/4 1.7 1.7e0 2.9E-4 +42 -7 #b001 #b001/100 #o777 #O777 #xabc55 #c(0 -5.6))
    +   (prompt-read "Artist" &rest)
    +   (or (parse-integer (prompt-read "Rating") :junk-allowed t) 0)
    +  (if x (format t "yes") (format t "no" nil) ;and here comment
    +  )
    +  ;; second line comment
    +  '(+ 1 2)
    +  (defvar *lines*)                ; list of all lines
    +  (position-if-not #'sys::whitespacep line :start beg))
    +  (quote (privet 1 2 3))
    +  '(hello world)
    +  (* 5 7)
    +  (1 2 34 5)
    +  (:use "aaaa")
    +  (let ((x 10) (y 20))
    +    (print (+ x y))
    +  )
    + +
    Ini file + +
    ;Settings relating to the location and loading of the database
    +[Database]
    +ProfileDir=.
    +ShowProfileMgr=smart
    +Profile1_Name[] = "\|/_-=MegaDestoyer=-_\|/"
    +DefaultProfile=True
    +AutoCreate = no
    +
    +[AutoExec]
    +Use="prompt"
    +Glob=autoexec_*.ini
    +AskAboutIgnoredPlugins=0
    +
    + +
    Apache + +
    # rewrite`s rules for wordpress pretty url
    +LoadModule rewrite_module  modules/mod_rewrite.so
    +RewriteCond %{REQUEST_FILENAME} !-f
    +RewriteCond %{REQUEST_FILENAME} !-d
    +RewriteRule . index.php [NC,L]
    +
    +ExpiresActive On
    +ExpiresByType application/x-javascript  "access plus 1 days"
    +
    +<Location /maps/>
    +  RewriteMap map txt:map.txt
    +  RewriteMap lower int:tolower
    +  RewriteCond %{REQUEST_URI} ^/([^/.]+)\.html$ [NC]
    +  RewriteCond ${map:${lower:%1}|NOT_FOUND} !NOT_FOUND
    +  RewriteRule .? /index.php?q=${map:${lower:%1}} [NC,L]
    +</Location>
    +
    + +
    nginx + +
    user  www www;
    +worker_processes  2;
    +pid /var/run/nginx.pid;
    +error_log  /var/log/nginx.error_log  debug | info | notice | warn | error | crit;
    +
    +events {
    +    connections   2000;
    +    use kqueue | rtsig | epoll | /dev/poll | select | poll;
    +}
    +
    +http {
    +    log_format main      '$remote_addr - $remote_user [$time_local] '
    +                         '"$request" $status $bytes_sent '
    +                         '"$http_referer" "$http_user_agent" '
    +                         '"$gzip_ratio"';
    +
    +    send_timeout 3m;
    +    client_header_buffer_size 1k;
    +
    +    gzip on;
    +    gzip_min_length 1100;
    +
    +    #lingering_time 30;
    +
    +    server {
    +        listen        one.example.com;
    +        server_name   one.example.com  www.one.example.com;
    +        access_log   /var/log/nginx.access_log  main;
    +
    +        location / {
    +            proxy_pass         http://127.0.0.1/;
    +            proxy_redirect     off;
    +            proxy_set_header   Host             $host;
    +            proxy_set_header   X-Real-IP        $remote_addr;
    +            charset            koi8-r;
    +        }
    +
    +        location ~* \.(jpg|jpeg|gif)$ {
    +            root         /spool/www;
    +        }
    +    }
    +}
    +
    + +
    Diff + +
    Index: languages/ini.js
    +===================================================================
    +--- languages/ini.js    (revision 199)
    ++++ languages/ini.js    (revision 200)
    +@@ -1,8 +1,7 @@
    + hljs.LANGUAGES.ini =
    + {
    +   case_insensitive: true,
    +-  defaultMode:
    +-  {
    ++  defaultMode: {
    +     contains: ['comment', 'title', 'setting'],
    +     illegal: '[^\\s]'
    +   },
    +
    +*** /path/to/original timestamp
    +--- /path/to/new      timestamp
    +***************
    +*** 1,3 ****
    +--- 1,9 ----
    ++ This is an important
    ++ notice! It should
    ++ therefore be located at
    ++ the beginning of this
    ++ document!
    +
    +! compress the size of the
    +! changes.
    +
    +  It is important to spell
    +
    + +
    DOS batch files + +
    cd \
    +copy a b
    +ping 192.168.0.1
    +@rem ping 192.168.0.1
    +net stop sharedaccess
    +del %tmp% /f /s /q
    +del %temp% /f /s /q
    +ipconfig /flushdns
    +taskkill /F /IM JAVA.EXE /T
    +
    +cd Photoshop/Adobe Photoshop CS3/AMT/
    +if exist application.sif (
    +    ren application.sif _application.sif
    +) else (
    +    ren _application.sif application.sif
    +)
    +
    +taskkill /F /IM proquota.exe /T
    +
    +sfc /SCANNOW
    +
    +set path = test
    +
    +xcopy %1\*.* %2
    +
    + +
    Bash + +
    #!/bin/bash
    +
    +###### BEGIN CONFIG
    +ACCEPTED_HOSTS="/root/.hag_accepted.conf"
    +BE_VERBOSE=false
    +###### END CONFIG
    +
    +if [ "$UID" -ne 0 ]
    +then
    + echo "Superuser rights is required"
    + echo 'Printing the # sign'
    + exit 2
    +fi
    +
    +genApacheConf(){
    + if [[ "$2" = "www" ]]
    + then
    +  full_domain=$1
    + else
    +  full_domain=$2.$1
    + fi
    + host_root="${APACHE_HOME_DIR}$1/$2"
    + echo -e "# Host $1/$2 :"
    +}
    +
    + +
    CMake + +
    project(test)
    +cmake_minimum_required(VERSION 2.6)
    +
    +# IF LINUX
    +if (${CMAKE_SYSTEM_NAME} MATCHES Linux)
    +    message("\nOS:\t\tLinux")
    +endif()
    +
    +# IF WINDOWS
    +if (${CMAKE_SYSTEM_NAME} MATCHES Windows)
    +    message("\nOS:\t\tWindows")
    +endif()
    +
    +set(test test0.cpp test1.cpp test2.cpp)
    +
    +include_directories(./)
    +
    +set(EXECUTABLE_OUTPUT_PATH ../bin)
    +
    +add_subdirectory(src)
    +
    +add_executable(test WIN32 ${test})
    +
    +target_link_libraries(test msimg32)
    +
    + +
    Axapta + +
    class ExchRateLoadBatch extends RunBaseBatch {
    +  ExchRateLoad rbc;
    +  container currencies;
    +  boolean actual;
    +  boolean overwrite;
    +  date beg;
    +  date end;
    +
    +  #define.CurrentVersion(5)
    +
    +  #localmacro.CurrentList
    +    currencies,
    +    actual,
    +    beg,
    +    end
    +  #endmacro
    +}
    +
    +public boolean unpack(container packedClass) {
    +  container       base;
    +  boolean         ret;
    +  Integer         version    = runbase::getVersion(packedClass);
    +
    +  switch (version) {
    +    case #CurrentVersion:
    +      [version, #CurrentList] = packedClass;
    +      return true;
    +    default:
    +      return false;
    +  }
    +  return ret;
    +}
    +
    + +
    1С + +
    
    +#Если Клиент Тогда
    +Перем СимвольныйКодКаталога = "ля-ля-ля"; //комментарий
    +Функция Сообщить(Знач ТекстСообщения, ТекстСообщения2) Экспорт //комментарий к функции
    +  x=ТекстСообщения+ТекстСообщения2+"
    +  |строка1
    +  |строка2
    +  |строка3";
    +КонецФункции
    +#КонецЕсли
    +
    +// Процедура ПриНачалеРаботыСистемы
    +//
    +Процедура ПриНачалеРаботыСистемы()
    +  Обработки.Помощник.ПолучитьФорму("Форма").Открыть();
    +  d = '21.01.2008'
    +КонецПроцедуры
    +
    + +
    AVR Assembler + +
    ;* Title:       Block Copy Routines
    +;* Version:     1.1
    +
    +.include "8515def.inc"
    +
    +    rjmp    RESET   ;reset handle
    +
    +.def    flashsize=r16       ;size of block to be copied
    +
    +flash2ram:
    +    lpm         ;get constant
    +    st  Y+,r0       ;store in SRAM and increment Y-pointer
    +    adiw    ZL,1        ;increment Z-pointer
    +    dec flashsize
    +    brne    flash2ram   ;if not end of table, loop more
    +    ret
    +
    +.def    ramtemp =r1     ;temporary storage register
    +.def    ramsize =r16        ;size of block to be copied
    +
    + +
    VHDL + +
    ------------------------------------
    +-- RS Trigger with Assynch. Reset --
    +------------------------------------
    +
    +library IEEE;
    +use IEEE.STD_LOGIC_1164.all;
    +
    +entity RS_AR is
    +	generic (T: Time := 0ns);
    +
    +	port(
    +		 -- Default RS Trigger
    +		 R  : in  STD_LOGIC;
    +		 S  : in  STD_LOGIC;
    +		 Q  : out STD_LOGIC;
    +		 nQ : out STD_LOGIC;
    +
    +		 -- Special Input Signals
    +		 AR : in  STD_LOGIC; -- assynch. reset
    +		 C  : in  STD_LOGIC  -- synch. signal
    +	     );
    +end RS_AR;
    +
    +
    +architecture RS_AR of RS_AR is
    +	signal QT: std_logic; -- Q(t)
    +begin
    +
    +	process(C, AR) is
    +		subtype RS is std_logic_vector ( 1 downto 0 );
    +	begin
    +		if AR='0' then
    +			QT <= '0';
    +		else
    +			if rising_edge(C) then
    +
    +				if not (R'stable(T) and S'stable(T)) then
    +				QT <= 'X';
    +				else
    +
    +				case RS'(R&S) is
    +					when "01" => QT <= '1';
    +					when "10" => QT <= '0';
    +					when "11" => QT <= 'X';
    +					when others => null;
    +				end case;
    +
    +				end if;
    +			end if;
    +		end if;
    +	end process;
    +
    +	Q  <= QT;
    +	nQ <= not QT;
    +
    +end RS_AR;
    +
    + +
    Parser 3 + +
    @CLASS
    +base
    +
    +@USE
    +module.p
    +
    +@BASE
    +class
    +
    +# Comment for code
    +@create[aParam1;aParam2][local1;local2]
    +  ^connect[mysql://host/database?ClientCharset=windows-1251]
    +  ^for[i](1;10){
    +    <p class="paragraph">^eval($i+10)</p>
    +    ^connect[mysql://host/database]{
    +      $tab[^table::sql{select * from `table` where a='1'}]
    +      $var_Name[some${value}]
    +    }
    +  }
    +
    +  ^rem{
    +    Multiline comment with code: $var
    +    ^while(true){
    +      ^for[i](1;10){
    +        ^sleep[]
    +      }
    +    }
    +  }
    +  ^taint[^#0A]
    +
    +@GET_base[]
    +## Comment for code
    +  # Isn't comment
    +  $result[$.hash_item1[one] $.hash_item2[two]]
    +
    + +
    TeX + +
    +\documentclass{article}
    +\usepackage[koi8-r]{inputenc}
    +\hoffset=0pt
    +\voffset=.3em
    +\tolerance=400
    +\newcommand{\eTiX}{\TeX}
    +\begin{document}
    +\section*{Highlight.js}
    +\begin{table}[c|c]
    +$\frac 12\, + \, \frac 1{x^3}\text{Hello \! world}$ & \textbf{Goodbye\~ world} \\\eTiX $ \pi=400 $
    +\end{table}
    +Ch\'erie, \c{c}a ne me pla\^\i t pas! % comment \b
    +G\"otterd\"ammerung~45\%=34.
    +$$
    +    \int\limits_{0}^{\pi}\frac{4}{x-7}=3
    +$$
    +\end{document}
    +
    + +
    Haskell + +
    +module Shapes
    +( Point(..)  ,
    +  Shape(..)  ,
    +  surface    ,
    +  baseCircle ,
    +  baseRect
    +) where
    +
    +-- Single line comment
    +{-
    +multi
    +line
    +comment
    +-}
    +data Point = Point Float Float deriving (Show)
    +data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
    +
    +surface :: Shape -> Float
    +surface (Circle _ r) = pi * r^2
    +surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
    +
    +baseCircle :: Float -> Shape
    +baseCircle r = Circle(Point 0 0) r
    +
    +baseRect :: Float -> Float -> Shape
    +baseRect w h = Rectangle (Point 0 0) (Point w h)
    +
    + +
    Erlang + +
    -module(ssh_cli).
    +
    +-behaviour(ssh_channel).
    +
    +-include("ssh.hrl").
    +%% backwards compatibility
    +-export([listen/1, listen/2, listen/3, listen/4, stop/1]).
    +
    +%% state
    +-record(state, {
    +	  cm,
    +	  channel
    +	 }).
    +
    +test(Foo)->Foo.
    +
    +init([Shell, Exec]) ->
    +    {ok, #state{shell = Shell, exec = Exec}};
    +init([Shell]) ->
    +    false = not true,
    +    io:format("Hello, \"~p!~n", [atom_to_list('World')]),
    +    {ok, #state{shell = Shell}}.
    +
    +concat([Single]) -> Single;
    +concat(RList) ->
    +    EpsilonFree = lists:filter(
    +        fun (Element) ->
    +            case Element of
    +                epsilon -> false;
    +                _ -> true
    +            end
    +        end,
    +        RList),
    +    case EpsilonFree of
    +        [Single] -> Single;
    +        Other -> {concat, Other}
    +    end.
    +
    +union_dot_union({union, _}=U1, {union, _}=U2) ->
    +    union(lists:flatten(
    +        lists:map(
    +            fun (X1) ->
    +                lists:map(
    +                    fun (X2) ->
    +                        concat([X1, X2])
    +                    end,
    +                    union_to_list(U2)
    +                )
    +            end,
    +            union_to_list(U1)
    +        ))).
    +
    + +
    Erlang REPL + +
    1> Str = "abcd".
    +"abcd"
    +2> L = test:length(Str).
    +4
    +3> Descriptor = {L, list_to_atom(Str)}.
    +{4,abcd}
    +4> L.
    +4
    +5> b().
    +Descriptor = {4,abcd}
    +L = 4
    +Str = "abcd"
    +ok
    +6> f(L).
    +ok
    +7> b().
    +Descriptor = {4,abcd}
    +Str = "abcd"
    +ok
    +8> {L, _} = Descriptor.
    +{4,abcd}
    +9> L.
    +4
    +10> 2#101.
    +5
    +11> 1.85e+3.
    +1850
    +
    + +
    Rust + +
    +use std;
    +
    +import std::io;
    +export fac, test1;
    +
    +123;                               // type int
    +123u;                              // type uint
    +123_u;                             // type uint
    +0xff00;                            // type int
    +0xff_u8;                           // type u8
    +0b1111_1111_1001_0000_i32;         // type i32
    +123.0;                             // type float
    +0.1;                               // type float
    +3f;                                // type float
    +0.1f32;                            // type f32
    +12E+99_f64;                        // type f64
    +
    +/* Factorial */
    +fn fac(n: int) -> int {
    +    let s: str = "This is
    +a multi-line string.
    +
    +It ends with an unescaped '\"'.";
    +    let c: char = 'Ф';
    +
    +    let result = 1, i = 1;
    +    while i <= n { // No parens around the condition
    +        result *= i;
    +        i += 1;
    +    }
    +    ret result;
    +}
    +
    +pure fn pure_length<T>(ls: list<T>) -> uint { /* ... */ }
    +
    +type t = map::hashtbl<int,str>;
    +let x = id::<int>(10);
    +
    +// Define some modules.
    +#[path = "foo.rs"]
    +mod foo;
    +
    +iface seq<T> {
    +    fn len() -> uint;
    +}
    +
    +impl <T> of seq<T> for [T] {
    +    fn len() -> uint { vec::len(self) }
    +    fn iter(b: fn(T)) {
    +        for elt in self { b(elt); }
    +    }
    +}
    +
    +enum list<T> {
    +    nil;
    +    cons(T, @list<T>);
    +}
    +
    +let a: list<int> = cons(7, @cons(13, @nil));
    +
    + +
    Matlab + +
    n = 20; % number of points
    +points = [random('unid', 100, n, 1), random('unid', 100, n, 1)];
    +len = zeros(1, n - 1);
    +points = sortrows(points);
    +%% Initial set of points
    +plot(points(:,1),points(:,2));
    +for i = 1: n-1
    +    len(i) = points(i + 1, 1) - points(i, 1);
    +end
    +while(max(len) > 2 * min(len))
    +    [d, i] = max(len);
    +    k = on_margin(points, i, d, -1);
    +    m = on_margin(points, i + 1, d, 1);
    +    xm = 0; ym = 0;
    +%% New point
    +    if(i == 1 || i + 1 == n)
    +        xm = mean(points([i,i+1],1))
    +        ym = mean(points([i,i+1],2))
    +    else
    +        [xm, ym] = dlg1(points([k, i, i + 1, m], 1), ...
    +            points([k, i, i + 1, m], 2))
    +    end
    +
    +    points = [ points(1:i, :); [xm, ym]; points(i + 1:end, :)];
    +end
    +
    +function [net] = get_fit_network(inputs, targets)
    +    % Create Network
    +    numHiddenNeurons = 20;  % Adjust as desired
    +    net = newfit(inputs,targets,numHiddenNeurons);
    +    net.trainParam.goal = 0.01;
    +    net.trainParam.epochs = 1000;
    +    % Train and Apply Network
    +    [net,tr] = train(net,inputs,targets);
    +end
    +
    + +
    + + + + +

    Special tests

    + + + + + + + + + + +
    Explicit Python highlighting + +
    for x in [1, 2, 3]:
    +  count(x)
    +
    + +
    Language set on <pre> + +
    for x in [1, 2, 3]:
    +  count(x)
    +
    + +
    HTML5-style language class (language-python) + +
    for x in [1, 2, 3]:
    +  count(x)
    +
    + +
    Replacing TAB with 4 spaces + +
    for x in [1, 2, 3]:
    +	count(x)
    +
    + +
    Custom markup + +
    <div id="contents">
    +  <p>Hello, World!Goodbye, cruel world!
    +</div>
    +
    + +
    Custom markup + TAB replacement + +
    for x in [1, 2, 3]:
    +	count(x)
    +	if x == 3:
    +		count(x + 1)
    +
    + +
    Non-pre container + +
    for x in [1, 2, 3]:
    +  count(x)
    +
    + + +
    Disabled highlighting + +
    <div id="contents">
    +  <p>Hello, World!
    +</div>
    +
    + +
    diff --git a/Scratch/js/index.js b/Scratch/js/index.js new file mode 100644 index 0000000..4b6c5a7 --- /dev/null +++ b/Scratch/js/index.js @@ -0,0 +1,150 @@ +function initCode() { + if ( ! /ip(od|hone)/.test(userAgent) ) { + if (! /chrome/.test(userAgent) ) { + // Disable animation in chrome + // It seems they have some progress to do :( + $('head').append(''); + } + } +} + +// --- Google analytics --- +function analytics() { + var admin = $.cookie('admin'); + if (! admin) { + // console.log("you're logged by google analytics"); + // add an event to all link for google analytics + $('a').click(function () { + // tell analytics to save event + try { + var identifier=$(this).attr('id') ; + var href=$(this).attr('href') + var label=""; + if ( typeof( identifier ) != 'undefined' ) { + label=label+'[id]:'+identifier + category='JSLink' + } + if ( typeof( href ) != 'undefined' ) { + label=label+' [href]:'+href + if ( href[0] == '#' ) { + category='Anchor'; + } else { + category='Link'; + } + } + _gaq.push(['_trackEvent', category, 'clicked', label]); + // console.log('[tracked]: ' + category + ' ; clicked ; ' + label ); + } + catch (err) { + console.log(err); + } + + // pause to allow google script to run + var date = new Date(); + var curDate = null; + do { + curDate = new Date(); + } while(curDate-date < 300); + }); + } else { + console.log("[WARNING] you're HIDDEN to analytics"); + } +} + +var userAgent; + +function detectClient() { + userAgent = navigator.userAgent.toLowerCase(); + /* + if (/msie/.test(userAgent) ) { + $('body').prepend('

    Avec Firefox , Safari ou Chrome cette page est bien plus jolie !

    This page is far nicer when opened with Firefox , Safari or Chrome!

    '); + }*/ + if (/ip(od|hone)/.test(userAgent)) { + $('head').append(''); + $('head').append(''); + } + return userAgent; +} + +// $(document).ready(function(){ +// var msgh1=$('#titre h1').html(); +// var msgh2=$('#titre h2').html(); +// var msgintro=$('.corps').html(); +// if (!msgh1) { msgh1=""; } +// if (!msgh2) { msgh2=""; } +// if (!msgintro) { msgintro=""; } +// $('#blackpage').prepend('
    '+msgh1+'
    '); +// $('#blackpage').prepend('
    '+msgh2+'
    '); +// $('#blackpage').append('
    '+msgintro+'
    '); +// }); + +var pref='/Scratch/css'; +var styleindex=0; +var styles=[ pref+'/scientific.css' + , pref+'/modern.css' + , pref+'/darkmodern.css']; + +function reloadStylesheets() { + var queryString = '?reload=' + new Date().getTime(); + $('link[rel="stylesheet"]').each(function () { + this.href = this.href.replace(/\?.*|$/, queryString); + }); +} +function switchCssTo(style) { + // try each style + styles.forEach(function(trystyle){ + if ($('link[href="'+trystyle+'"]').length > 0) { + $('link[href="'+trystyle+'"]').attr('href',style); + styleindex=styles.indexOf(style); + } + }); + // save the preference + $.cookie('css',style); +} +// Ability to switch css by clicking on #swtichcss +function switchcss() { + // If the user has saved a preference + // load its preferred style + if ( $.cookie('css') !== null ) { + setTimeout(function(){switchCssTo($.cookie('css'));}, 1000); + } + $('#switchcss').click(function(){ + console.log("Switched to " + styles[styleindex+1 % styles.length]); + switchCssTo(styles[ (styleindex+1) % styles.length ]); + }); +} + +// Ce que l'on va lancer à l'init. +$(document).ready(function() { + var client=detectClient(); + if ( ! /msie/.test(client) ) { initCode(); } + $('#blackpage').fadeOut('slow',function(){ $('#blackpage').remove(); }); + analytics(); + switchcss(); +}); + +$(window).bind("load", function() { + // lorsque toutes les ressources ont ete chargees + if (/windows/.test(navigator.userAgent.toLowerCase())) { + $('head').append(''); + } else { + $('head').append(''); + } +}); + + +// --- Google Analytics --- +if ( ! $.cookie('admin') ) { + var _gaq = _gaq || []; + _gaq.push(['_setAccount', 'UA-10612400-1']); + _gaq.push(['_trackPageview']); + + (function() { + var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; + ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; + var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); + })(); +} else { + console.log("[WARNING] you're HIDDEN to analytics"); +} + diff --git a/Scratch/js/jquery-1.3.1.min.js b/Scratch/js/jquery-1.3.1.min.js new file mode 100644 index 0000000..c327fae --- /dev/null +++ b/Scratch/js/jquery-1.3.1.min.js @@ -0,0 +1,19 @@ +/* + * jQuery JavaScript Library v1.3.1 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-01-21 20:42:16 -0500 (Wed, 21 Jan 2009) + * Revision: 6158 + */ +(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.makeArray(E))},selector:"",jquery:"1.3.1",size:function(){return this.length},get:function(E){return E===g?o.makeArray(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,find:function(E){if(this.length===1&&!/,/.test(E)){var G=this.pushStack([],"find",E);G.length=0;o.find(E,this[0],G);return G}else{var F=o.map(this,function(H){return o.find(E,H)});return this.pushStack(/[^+>] [^+>]/.test(E)?o.unique(F):F,"find",E)}},clone:function(F){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.cloneNode(true),H=document.createElement("div");H.appendChild(I);return o.clean([H.innerHTML])[0]}else{return this.cloneNode(true)}});var G=E.find("*").andSelf().each(function(){if(this[h]!==g){this[h]=null}});if(F===true){this.find("*").andSelf().each(function(I){if(this.nodeType==3){return}var H=o.data(this,"events");for(var K in H){for(var J in H[K]){o.event.add(G[I],K,H[K][J],H[K][J].data)}}})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var F=o.expr.match.POS.test(E)?o(E):null;return this.map(function(){var G=this;while(G&&G.ownerDocument){if(F?F.index(G)>-1:o(G).is(E)){return G}G=G.parentNode}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML:null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(K,N,M){if(this[0]){var J=(this[0].ownerDocument||this[0]).createDocumentFragment(),G=o.clean(K,(this[0].ownerDocument||this[0]),J),I=J.firstChild,E=this.length>1?J.cloneNode(true):J;if(I){for(var H=0,F=this.length;H0?E.cloneNode(true):J)}}if(G){o.each(G,z)}}return this;function L(O,P){return N&&o.nodeName(O,"table")&&o.nodeName(P,"tr")?(O.getElementsByTagName("tbody")[0]||O.appendChild(O.ownerDocument.createElement("tbody"))):O}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(G,E,I){if(E=="width"||E=="height"){var K,F={position:"absolute",visibility:"hidden",display:"block"},J=E=="width"?["Left","Right"]:["Top","Bottom"];function H(){K=E=="width"?G.offsetWidth:G.offsetHeight;var M=0,L=0;o.each(J,function(){M+=parseFloat(o.curCSS(G,"padding"+this,true))||0;L+=parseFloat(o.curCSS(G,"border"+this+"Width",true))||0});K-=Math.round(M+L)}if(o(G).is(":visible")){H()}else{o.swap(G,F,H)}return Math.max(0,K)}return o.curCSS(G,E,I)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,R){if(typeof R==="number"){R+=""}if(!R){return}if(typeof R==="string"){R=R.replace(/(<(\w+)[^>]*?)\/>/g,function(T,U,S){return S.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?T:U+">"});var O=o.trim(R).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
    "]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
    ","
    "]||[0,"",""];L.innerHTML=Q[1]+R+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var N=!O.indexOf(""&&O.indexOf("=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(R)){L.insertBefore(K.createTextNode(R.match(/^\s*/)[0]),L.firstChild)}R=o.makeArray(L.childNodes)}if(R.nodeType){G.push(R)}else{G=o.merge(G,R)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E*",this).remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); +/* + * Sizzle CSS Selector Engine - v0.9.3 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){var Q=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]+['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,K=0,G=Object.prototype.toString;var F=function(X,T,aa,ab){aa=aa||[];T=T||document;if(T.nodeType!==1&&T.nodeType!==9){return[]}if(!X||typeof X!=="string"){return aa}var Y=[],V,ae,ah,S,ac,U,W=true;Q.lastIndex=0;while((V=Q.exec(X))!==null){Y.push(V[1]);if(V[2]){U=RegExp.rightContext;break}}if(Y.length>1&&L.exec(X)){if(Y.length===2&&H.relative[Y[0]]){ae=I(Y[0]+Y[1],T)}else{ae=H.relative[Y[0]]?[T]:F(Y.shift(),T);while(Y.length){X=Y.shift();if(H.relative[X]){X+=Y.shift()}ae=I(X,ae)}}}else{var ad=ab?{expr:Y.pop(),set:E(ab)}:F.find(Y.pop(),Y.length===1&&T.parentNode?T.parentNode:T,P(T));ae=F.filter(ad.expr,ad.set);if(Y.length>0){ah=E(ae)}else{W=false}while(Y.length){var ag=Y.pop(),af=ag;if(!H.relative[ag]){ag=""}else{af=Y.pop()}if(af==null){af=T}H.relative[ag](ah,af,P(T))}}if(!ah){ah=ae}if(!ah){throw"Syntax error, unrecognized expression: "+(ag||X)}if(G.call(ah)==="[object Array]"){if(!W){aa.push.apply(aa,ah)}else{if(T.nodeType===1){for(var Z=0;ah[Z]!=null;Z++){if(ah[Z]&&(ah[Z]===true||ah[Z].nodeType===1&&J(T,ah[Z]))){aa.push(ae[Z])}}}else{for(var Z=0;ah[Z]!=null;Z++){if(ah[Z]&&ah[Z].nodeType===1){aa.push(ae[Z])}}}}}else{E(ah,aa)}if(U){F(U,T,aa,ab)}return aa};F.matches=function(S,T){return F(S,null,null,T)};F.find=function(Z,S,aa){var Y,W;if(!Z){return[]}for(var V=0,U=H.order.length;V":function(X,T,Y){if(typeof T==="string"&&!/\W/.test(T)){T=Y?T:T.toUpperCase();for(var U=0,S=X.length;U=0){if(!U){S.push(X)}}else{if(U){T[W]=false}}}}return false},ID:function(S){return S[1].replace(/\\/g,"")},TAG:function(T,S){for(var U=0;S[U]===false;U++){}return S[U]&&P(S[U])?T[1]:T[1].toUpperCase()},CHILD:function(S){if(S[1]=="nth"){var T=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(S[2]=="even"&&"2n"||S[2]=="odd"&&"2n+1"||!/\D/.test(S[2])&&"0n+"+S[2]||S[2]);S[2]=(T[1]+(T[2]||1))-0;S[3]=T[3]-0}S[0]="done"+(K++);return S},ATTR:function(T){var S=T[1].replace(/\\/g,"");if(H.attrMap[S]){T[1]=H.attrMap[S]}if(T[2]==="~="){T[4]=" "+T[4]+" "}return T},PSEUDO:function(W,T,U,S,X){if(W[1]==="not"){if(W[3].match(Q).length>1){W[3]=F(W[3],null,null,T)}else{var V=F.filter(W[3],T,U,true^X);if(!U){S.push.apply(S,V)}return false}}else{if(H.match.POS.test(W[0])){return true}}return W},POS:function(S){S.unshift(true);return S}},filters:{enabled:function(S){return S.disabled===false&&S.type!=="hidden"},disabled:function(S){return S.disabled===true},checked:function(S){return S.checked===true},selected:function(S){S.parentNode.selectedIndex;return S.selected===true},parent:function(S){return !!S.firstChild},empty:function(S){return !S.firstChild},has:function(U,T,S){return !!F(S[3],U).length},header:function(S){return/h\d/i.test(S.nodeName)},text:function(S){return"text"===S.type},radio:function(S){return"radio"===S.type},checkbox:function(S){return"checkbox"===S.type},file:function(S){return"file"===S.type},password:function(S){return"password"===S.type},submit:function(S){return"submit"===S.type},image:function(S){return"image"===S.type},reset:function(S){return"reset"===S.type},button:function(S){return"button"===S.type||S.nodeName.toUpperCase()==="BUTTON"},input:function(S){return/input|select|textarea|button/i.test(S.nodeName)}},setFilters:{first:function(T,S){return S===0},last:function(U,T,S,V){return T===V.length-1},even:function(T,S){return S%2===0},odd:function(T,S){return S%2===1},lt:function(U,T,S){return TS[3]-0},nth:function(U,T,S){return S[3]-0==T},eq:function(U,T,S){return S[3]-0==T}},filter:{CHILD:function(S,V){var Y=V[1],Z=S.parentNode;var X=V[0];if(Z&&(!Z[X]||!S.nodeIndex)){var W=1;for(var T=Z.firstChild;T;T=T.nextSibling){if(T.nodeType==1){T.nodeIndex=W++}}Z[X]=W-1}if(Y=="first"){return S.nodeIndex==1}else{if(Y=="last"){return S.nodeIndex==Z[X]}else{if(Y=="only"){return Z[X]==1}else{if(Y=="nth"){var ab=false,U=V[2],aa=V[3];if(U==1&&aa==0){return true}if(U==0){if(S.nodeIndex==aa){ab=true}}else{if((S.nodeIndex-aa)%U==0&&(S.nodeIndex-aa)/U>=0){ab=true}}return ab}}}}},PSEUDO:function(Y,U,V,Z){var T=U[1],W=H.filters[T];if(W){return W(Y,V,U,Z)}else{if(T==="contains"){return(Y.textContent||Y.innerText||"").indexOf(U[3])>=0}else{if(T==="not"){var X=U[3];for(var V=0,S=X.length;V=0:V==="~="?(" "+X+" ").indexOf(T)>=0:!U[4]?S:V==="!="?X!=T:V==="^="?X.indexOf(T)===0:V==="$="?X.substr(X.length-T.length)===T:V==="|="?X===T||X.substr(0,T.length+1)===T+"-":false},POS:function(W,T,U,X){var S=T[2],V=H.setFilters[S];if(V){return V(W,U,T,X)}}}};var L=H.match.POS;for(var N in H.match){H.match[N]=RegExp(H.match[N].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(T,S){T=Array.prototype.slice.call(T);if(S){S.push.apply(S,T);return S}return T};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(M){E=function(W,V){var T=V||[];if(G.call(W)==="[object Array]"){Array.prototype.push.apply(T,W)}else{if(typeof W.length==="number"){for(var U=0,S=W.length;U";var S=document.documentElement;S.insertBefore(T,S.firstChild);if(!!document.getElementById(U)){H.find.ID=function(W,X,Y){if(typeof X.getElementById!=="undefined"&&!Y){var V=X.getElementById(W[1]);return V?V.id===W[1]||typeof V.getAttributeNode!=="undefined"&&V.getAttributeNode("id").nodeValue===W[1]?[V]:g:[]}};H.filter.ID=function(X,V){var W=typeof X.getAttributeNode!=="undefined"&&X.getAttributeNode("id");return X.nodeType===1&&W&&W.nodeValue===V}}S.removeChild(T)})();(function(){var S=document.createElement("div");S.appendChild(document.createComment(""));if(S.getElementsByTagName("*").length>0){H.find.TAG=function(T,X){var W=X.getElementsByTagName(T[1]);if(T[1]==="*"){var V=[];for(var U=0;W[U];U++){if(W[U].nodeType===1){V.push(W[U])}}W=V}return W}}S.innerHTML="";if(S.firstChild&&S.firstChild.getAttribute("href")!=="#"){H.attrHandle.href=function(T){return T.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var S=F,T=document.createElement("div");T.innerHTML="

    ";if(T.querySelectorAll&&T.querySelectorAll(".TEST").length===0){return}F=function(X,W,U,V){W=W||document;if(!V&&W.nodeType===9&&!P(W)){try{return E(W.querySelectorAll(X),U)}catch(Y){}}return S(X,W,U,V)};F.find=S.find;F.filter=S.filter;F.selectors=S.selectors;F.matches=S.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){H.order.splice(1,0,"CLASS");H.find.CLASS=function(S,T){return T.getElementsByClassName(S[1])}}function O(T,Z,Y,ac,aa,ab){for(var W=0,U=ac.length;W0){W=S;break}}}S=S[T]}ab[V]=W}}}var J=document.compareDocumentPosition?function(T,S){return T.compareDocumentPosition(S)&16}:function(T,S){return T!==S&&(T.contains?T.contains(S):true)};var P=function(S){return S.nodeType===9&&S.documentElement.nodeName!=="HTML"||!!S.ownerDocument&&P(S.ownerDocument)};var I=function(S,Z){var V=[],W="",X,U=Z.nodeType?[Z]:Z;while((X=H.match.PSEUDO.exec(S))){W+=X[0];S=S.replace(H.match.PSEUDO,"")}S=H.relative[S]?S+"*":S;for(var Y=0,T=U.length;Y=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
    ").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}this[H].style.display=o.data(this[H],"olddisplay",K)}}return this}},hide:function(H,I){if(H){return this.animate(t("hide",3),H,I)}else{for(var G=0,F=this.length;G=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)==1){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
    ';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(H,F){var E=H?"Left":"Top",G=H?"Right":"Bottom";o.fn["inner"+F]=function(){return this[F.toLowerCase()]()+j(this,"padding"+E)+j(this,"padding"+G)};o.fn["outer"+F]=function(J){return this["inner"+F]()+j(this,"border"+E+"Width")+j(this,"border"+G+"Width")+(J?j(this,"margin"+E)+j(this,"margin"+G):0)};var I=F.toLowerCase();o.fn[I]=function(J){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+F]||document.body["client"+F]:this[0]==document?Math.max(document.documentElement["client"+F],document.body["scroll"+F],document.documentElement["scroll"+F],document.body["offset"+F],document.documentElement["offset"+F]):J===g?(this.length?o.css(this[0],I):null):this.css(I,typeof J==="string"?J:J+"px")}})})(); \ No newline at end of file diff --git a/Scratch/js/jquery.cookie.js b/Scratch/js/jquery.cookie.js new file mode 100644 index 0000000..a80bfa2 --- /dev/null +++ b/Scratch/js/jquery.cookie.js @@ -0,0 +1,97 @@ +/** + * Cookie plugin + * + * Copyright (c) 2006 Klaus Hartl (stilbuero.de) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ + +/** + * Create a cookie with the given name and value and other optional parameters. + * + * @example $.cookie('the_cookie', 'the_value'); + * @desc Set the value of a cookie. + * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true }); + * @desc Create a cookie with all available options. + * @example $.cookie('the_cookie', 'the_value'); + * @desc Create a session cookie. + * @example $.cookie('the_cookie', null); + * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain + * used when the cookie was set. + * + * @param String name The name of the cookie. + * @param String value The value of the cookie. + * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. + * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. + * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. + * If set to null or omitted, the cookie will be a session cookie and will not be retained + * when the the browser exits. + * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). + * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). + * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will + * require a secure protocol (like HTTPS). + * @type undefined + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ + +/** + * Get the value of a cookie with the given name. + * + * @example $.cookie('the_cookie'); + * @desc Get the value of a cookie. + * + * @param String name The name of the cookie. + * @return The value of the cookie. + * @type String + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ +jQuery.cookie = function(name, value, options) { + if (typeof value != 'undefined') { // name and value given, set cookie + options = options || {}; + if (value === null) { + value = ''; + options = $.extend({}, options); // clone object since it's unexpected behavior if the expired property were changed + options.expires = -1; + } + var expires = ''; + if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { + var date; + if (typeof options.expires == 'number') { + date = new Date(); + date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); + } else { + date = options.expires; + } + expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE + } + // NOTE Needed to parenthesize options.path and options.domain + // in the following expressions, otherwise they evaluate to undefined + // in the packed version for some reason... + var path = options.path ? '; path=' + (options.path) : ''; + var domain = options.domain ? '; domain=' + (options.domain) : ''; + var secure = options.secure ? '; secure' : ''; + document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); + } else { // only name given, get cookie + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } +}; \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..74f550a --- /dev/null +++ b/index.html @@ -0,0 +1,100 @@ + + + + + + YBlog - Home + + + + + + + + + + + + +