diff --git a/app/Shakefile.hs b/app/Shakefile.hs index 6b38e66..3e486ec 100644 --- a/app/Shakefile.hs +++ b/app/Shakefile.hs @@ -144,7 +144,11 @@ buildRules = do ".html" -> do if out == siteDir "index.html" then buildArchive getPosts getTemplate out - else genHtmlAction getPost getTemplate out + else do + htmlExists <- doesFileExist (srcDir asset) + if htmlExists + then copyFileChanged (srcDir asset) out + else genHtmlAction getPost getTemplate out ".pdf" -> do txtExists <- doesFileExist (srcDir asset) if txtExists @@ -183,7 +187,8 @@ buildArchive getPosts getTemplate out = do title = "#+title: Yann Esposito's blog" menu = "@@html:Home | Slides | About@@" articleList = toS $ T.intercalate "\n" $ map postInfo posts - fileContent = title <> "\n\n" <> menu <> "\n\n" <> welcomeTxt <> "\n\n" <> articleList + olderArchives = "---\n\n@@html:Older Archives from my previous blog@@" + fileContent = title <> "\n\n" <> menu <> "\n\n" <> welcomeTxt <> "\n\n" <> articleList <> olderArchives eitherResult <- liftIO $ Pandoc.runIO $ Readers.readOrg (def { readerStandalone = True }) (toS fileContent) bp <- case eitherResult of Left _ -> fail "BAD" diff --git a/engine/ye-com-fastpublish.hs b/engine/ye-com-fastpublish.hs new file mode 100755 index 0000000..a641d40 --- /dev/null +++ b/engine/ye-com-fastpublish.hs @@ -0,0 +1,90 @@ +#!/usr/bin/env stack +{- stack + --resolver lts-6.12 + --install-ghc + runghc + --package turtle + --package ansi-terminal + --verbosity s +-} + + {-# LANGUAGE OverloadedStrings #-} +import Turtle + +import Prelude hiding (FilePath) +import qualified Control.Foldl as Fold +import Data.Maybe (fromMaybe) +import System.Console.ANSI +import Control.Exception (catches,Handler(..)) + +main = mainProc `catches` [ Handler handleShellFailed + , Handler handleProcFailed + ] + +handleShellFailed :: ShellFailed -> IO () +handleShellFailed (ShellFailed cmdLine _) = do + setSGR [SetColor Foreground Dull Red] + echo $ ("[FAILED]: " <> cmdLine) + setSGR [Reset] +handleProcFailed :: ProcFailed -> IO () +handleProcFailed (ProcFailed procCommand procArgs _) = do + setSGR [SetColor Foreground Dull Red] + echo $ ("[FAILED]: " <> procCommand <> (mconcat procArgs)) + setSGR [Reset] + + +mainProc :: IO () +mainProc = do + -- So we can't have access to $0 in Haskell via stack. + -- Too bad. + -- So instead, I'll check I'm in the right directory. + debug "Checking directory" + (hakylldir,pubdir) <- checkDir + debug "Retrieving revision number" + rev <- fold (inshell "git rev-parse --short HEAD" empty) Fold.head + debug ("Revision number retrieved: " <> fromMaybe "unknow" rev) + debug $ "cd " <> (format fp pubdir) + cd pubdir + pwd >>= echo . format fp + dshells "git init ." + dshell ("git remote add upstream " <> mainRepository) + dshells "git fetch upstream" + dshells "git reset upstream/gh-pages" + dshells "git add -A ." + echo "Commit and publish" + dshells ("git commit -m \"publishing at rev " <> (fromMaybe "unknow" rev) <> "\"") + echo "Don't `git push` this time" + dshells "git push -q upstream HEAD:gh-pages" + +debug txt = do + setSGR [SetColor Foreground Dull Yellow] + echo txt + setSGR [Reset] + +dshells x = do + debug x + shells x empty + +dshell x = do + debug x + shell x empty + +checkDir :: IO (FilePath,FilePath) +checkDir = do + toolsExists <- testdir "tools" + if (not toolsExists) + then exit (ExitFailure 1) + else return (".","content/_site") + +mainRepository = "git@github.com:yogsototh/yannesposito.com.git" + +cloneIfNeeded :: FilePath -> IO () +cloneIfNeeded pubdir = do + contentExists <- testdir pubdir + when (not contentExists) $ + procs "git" + [ "clone" + , "-b", "gh-pages" + , mainRepository + , format fp pubdir] + empty diff --git a/src/CNAME b/src/CNAME new file mode 100644 index 0000000..c6f0806 --- /dev/null +++ b/src/CNAME @@ -0,0 +1 @@ +yannesposito.com diff --git a/src/Scratch/css/brutalist.css b/src/Scratch/css/brutalist.css new file mode 100644 index 0000000..8054370 --- /dev/null +++ b/src/Scratch/css/brutalist.css @@ -0,0 +1 @@ +body{font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;font-size:14px;line-height:1.5em;padding:0 1em}code{font-family:"Lucida Console", Monaco, monospace;font-size:1em}pre,pre code{font-family:"Lucida Console", Monaco, monospace;font-size:13px;line-height:1.25em}i,em{font-style:italic}b,strong,h1,h2,h3,h4,h5,h6{font-weight:bold}h1,h2,h3,h4,h5,h6{padding:0 16px;clear:both}table.description tr td{border:1px solid #eeeef1}body{color:#002b36;background-color:#FAFAFC}::selection{background:#6c71c4;color:white}::-moz-selection{background:#6c71c4;color:white}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:"-";display:inline-block;margin-left:2.5ex;width:0}ul{padding-left:0;text-indent:-2.5ex}ol{padding-left:0}.toc{line-height:1em}.toc ol li,.toc ul li{margin:.5em 0}ol li ol,ul li ol,ul li ul{margin:0em 2em 0.6em;list-style:none}body,h1,h2,h3,h4,#entete,.tagname{text-rendering:optimizelegibility;line-height:1.5em}#header,#bottom,#navigation{text-align:center}#header a,#bottom a,#navigation a{border-bottom:2px solid transparent}#header a:hover,#bottom a:hover,#navigation a:hover{border-color:#002b36}#navigation{margin:1em auto;font-size:1.25em;text-align:center;font-weight:thin;font-style:italic}#navigation .sep{opacity:.3;font-style:italic;display:inline-block;margin:0 .5em}#navigation a:before{content:"/"}#choix{height:0}.article #choix{height:auto}@media only screen and (max-width: 600px){#choix{height:auto}}@media only screen and (max-width: 480px){#navigation>a{width:100%;display:inline-block}#navigation .sep{display:none}}.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{border-left:dashed 1px #93a1a1;padding:1em;margin:1em 0}pre>code{display:block}.corps blockquote{max-width:80%;margin:0 auto;padding:0;font-weight:bold;max-width:80%;margin:0 auto;padding:0;color:#073642;line-height:1.5em;padding-left:1.5em;border-left:solid}.corps blockquote ul,.corps blockquote ol{margin-left:0}.corps blockquote a{font-weight:normal}.corps blockquote a:hover{color:#cb4b16}.corps blockquote i,.corps blockquote em{font-weight:normal;font-style:normal;color:#859900}.corps blockquote strong,.corps blockquote b{font-weight:bold;color:#002b36}.corps blockquote>ul,.corps blockquote>ol{padding-left:1.5em}abbr,acronym{text-decoration:none;border-bottom-width:0}abbr:after,acronym:after{content:"*";vertical-align:super;line-height:0;font-size:.66em;color:#073642}#titre{margin:2em 0}#liens .active,#sousliens{color:#002b36;border:#93a1a1 solid 1px;background-color:#F0F0F3}#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 #EEE;background:rgba(0,0,0,0.05);border:1px solid rgba(0,0,0,0.1)}#liens a:hover{background:rgba(0,0,0,0.1)}#liens .active{text-shadow:0 0 2px rgba(0,0,0,0.5);background-color:#f7f7f9;border:1px solid #e9e9eb;border-top:none}#lastmod{font-size:0.9em}.nojsbutton{font-size:2.5em}#clickcomment,#choixrss>a,.clearbutton{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,.clearbutton:hover{max-width:80%;margin:0 auto;padding:0;color:#dc5c27;text-shadow:0 0 2px #FAA}#clickcomment:hover ul,#clickcomment:hover ol,#choixrss>a:hover ul,#choixrss>a:hover ol,.clearbutton:hover ul,.clearbutton:hover ol{margin-left:0}#clickcomment:active,#choixrss>a:active,.clearbutton:active{max-width:80%;margin:0 auto;padding:0;color:#dc5c27;text-shadow:0 0 2px #FAA;background:#f4f4f6}#clickcomment:active ul,#clickcomment:active ol,#choixrss>a:active ul,#choixrss>a:active ol,.clearbutton:active ul,.clearbutton:active ol{margin-left:0}.return>a,#choixrss>a{float:right}#choix .return>a,#choix #choixrss>a{margin-top:0}#choix #choixlang{position:absolute;left:0;top:0}#choix #choixlang a{margin-top:0;width:100%}.small{font-size:0.8em}.tiny{font-size:0.6em}.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:#93a1a1 solid 1px;background-color:#F0F0F3}.date .year strong,.date .year b,.date .year i,.date .year em{color:#002b36}body{text-align:left;max-width:50em;margin:0 auto}body>#entete{position:absolute;left:0;top:0.5em;width:100%;min-width:48em;z-index:8000;padding-bottom:1em;margin-bottom:3em}#titre h2{width:80%;margin-left:auto;margin-right:auto;text-align:center;font-style:italic}#titre{text-align:center;width:100%}#titre h1,#titre h2{padding-left:1em;padding-right:1em}#bottom{opacity:.25;clear:right;margin-right:0;padding:1.5em;border-top:solid 2px #93a1a1;line-height:1.5em;color:#224d58;margin-top:2em;text-align:center}#bottom a{color:#113c47}#bottom a:hover{color:#cb4b16}#bottom:hover{opacity:1}#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:0;margin-bottom:0}#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{margin-left:auto;margin-right:auto;margin-top:0;position:relative;clear:both;width:100%;max-width:50em}@media only screen and (max-width: 48em){#content{padding:0;width:100%}#content img{max-width:80%}#content .corps{padding:0 1em}}.encadre,.black,.intro,.resume,.shadow{padding:2em;margin-top:2em;margin-bottom:2em}.encadre,.black,.shadow{color:#002b36;border:#93a1a1 solid 1px;background-color:#F0F0F3}.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}.corps .intro,.corps .resume{max-width:80%;margin:0 auto}#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{text-align:justify;margin:0;clear:both}.corps p,.corps ol,.corps ul,.corps blockquote,.corps>pre,.corps>code{margin-top:1em;margin-bottom:1em}.corps p>code,.corps p>a>code,.corps li>code,.corps li>a>code{display:inline-block;line-height:1em;margin:0}.corps li>code,.corps li>a>code{text-indent:0}.corps blockquote>ul,.corps ul li blockquote>ul,.corps ol li blockquote>ul{margin:0}.corps ul li ul,.corps ol li ul,.corps ul li ol,.corps ol li ol{margin:0 2em 0.6em}.corps img{max-width:80%;border:1px solid #93a1a1}.corps p>a>img{display:inline-block;border:none;padding:0;margin:0}.corps a:hover img{border-color:#cb4b16}figure,.figure{color:#002b36;border:#93a1a1 solid 1px;background-color:#F0F0F3;border:2px solid #93a1a1;margin:3em 0;text-align:center}figure strong,figure b,figure i,figure em,.figure strong,.figure b,.figure i,.figure em{color:#002b36}figure figcaption,figure .caption,.figure figcaption,.figure .caption{text-align:center;margin:.5em 0}figure.left,figure.right,.figure.right,.figure.left{max-width:30%}img.clean{border:none;background-color: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-style:italic;padding-top:8em;z-index:9000;cursor:wait}#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:50em;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:.6em;margin-left:2em}img.left{float:left;max-width:30%;margin-top:.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}.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}.corps .return a{color:#eeeef1;padding:0.1em;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:#073642}.corps .return a:hover{color:#cb4b16}.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:#073642}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:#073642}a:hover strong,a:hover b,a:hover i,a:hover em{color:#dc5c27}a:hover .nicer{color:#ffb17c}.nicer{color:#ccccd0}.and{color:#ccccd0}.block{max-width:80%;margin:0 auto;padding:0;width:26.5%;padding:1em;text-align:left;line-height:1em;margin-left:1%;margin-right:1%;font-size:0.8em;height:9em}.block ul,.block ol{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{font-style:italic;color:#073642}.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:.8em;background:#F0F0F3;line-height:2.5em;padding:2em;text-align:justify}.pala{opacity:0.6}sup{vertical-align:top;font-size:.7em}.default .corps p a,.default .corps ul a{color:#073642}.default .corps p a:hover,.default .corps ul a:hover{color:#cb4b16}.article .corps a:after{content:"†";vertical-align:super;line-height:0;font-size:.66em;color:#073642}.article .corps pre a:after{content:""}.article .corps .nostar a:after{content:""}.article .corps .footnotes a:after,.article .corps sup a:after{content:""}.article .corps sup a{font-weight:bold;padding:0 .3em;margin-left:2px;vertical-align:top}.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{margin:1.5em auto;font-size:.8em;border:1px solid #93a1a1}table tr td{padding:2px .5em}table tr:nth-child(odd){background-color:#F0F0F3}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{border:dotted 1px rgba(0,0,0,0.3);padding:2px;line-height:1em}.social:hover{color:#268bd2}#blogpage .popularblock{width:33.3333%;min-width:150px;float:left;font-weight:bold}#blogpage .popularblock:hover figure{color:#cb4b16;padding:20px}#blogpage .popularblock figure{margin:0 10px;padding:20px;height:12em}#blogpage .popularblock figure img{max-width:80%;max-height:6em}ul.sameline{list-style:none}ul.sameline li{float:left;margin-left:.5em}.resumearticle{background-color:#F0F0F3;margin:1em 0;padding:1em}a.cut{font-size:12px;text-align:right;display:block;width:100%;opacity:.5;border:1px solid #FAFAFC;overflow:hidden;text-overflow:ellipsis}a.cut:hover{opacity:1}a.cut strong{font-weight:bold}.codehighlight pre{border-left:8px solid}#social{font-size:1.5em;text-align:center;padding-top:1em;margin-top:1em;border-top:2px solid #93a1a1;opacity:.3}#social:hover{opacity:1}#social .message{font-size:10px}#totop{text-align:center}.corps .inlineblockimg{width:48px;margin:8px;text-align:center;vertical-align:middle;display:inline-block;line-height:0.8em;overflow:hidden;border:solid 1px}.corps img.inlineimage{display:block;padding:0;max-height:48px;max-width:48px;border:none;margin:0 auto;vertical-align:middle;font-size:7px;overflow:hidden}section.slide{border-color:#93a1a1;border:solid 1px;margin-bottom:1em;padding:.5em;font-size:.8em;min-height:25em}section.slide ul,section.slide ol{padding-left:1.5ex}a.rss{background-color:#F8F8F8;background-image:-moz-linear-gradient(center top, white, #DEDEDE);border:1px solid #CCCCCC;color:#cb4b16;cursor:pointer;display:inline-block;font-size:13px;font-weight:bold;height:20px;line-height:20px;overflow:hidden;padding:0 5px;position:relative;text-shadow:0 1px 0 rgba(255,255,255,0.5)}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}.highlight{background-color:#eee8d5;padding:0 3px;height:1.1em;line-height:1em;display:inline-block}#header{opacity:.25;border-bottom:solid 4px black}#header:hover{opacity:1}.strike{text-decoration:line-through}.wrap{word-wrap:break-word}.cssdebug pre>code:before{content:"----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80***85***90";display:block} diff --git a/src/Scratch/css/pandoc-solarized.css b/src/Scratch/css/pandoc-solarized.css new file mode 100644 index 0000000..02d0227 --- /dev/null +++ b/src/Scratch/css/pandoc-solarized.css @@ -0,0 +1 @@ +pre code{color:#073642}code span.kw,code span a.kw{color:#859900;font-weight:normal;font-style:normal}code span.dt,code span a.dt{color:#B58900;font-weight:normal;font-style:normal}code span.dv,code span.bn,code span.fl,code span.ch,code span.st,code span a.dv,code span a.bn,code span a.fl,code span a.ch,code span a.st{color:#2AA198;font-weight:normal;font-style:normal}code span.co,code span a.co{color:#93A1A1;font-weight:normal;font-style:italic}code span.ot,code span a.ot{color:#268BD2;font-weight:normal;font-style:normal}code span.al,code span a.al{color:#DC322F;font-weight:normal;font-style:normal}code span.fu,code span a.fu{color:#268BD2;font-weight:normal;font-style:normal}code span.er,code span a.er{color:#DC322F;font-weight:normal;font-style:normal}code span.wa,code span a.wa{color:#CB4B16;font-weight:normal;font-style:italic}code span.cn,code span a.cn{color:#2AA198;font-weight:normal;font-style:normal}code span.sc,code span a.sc{color:#DC322F;font-weight:normal;font-style:normal}code span.vs,code span a.vs{color:#657B83;font-weight:normal;font-style:normal}code span.ss,code span a.ss{color:#DC322F;font-weight:normal;font-style:normal}code span.im,code span a.im{color:#657B83;font-weight:normal;font-style:normal}code span.va,code span a.va{color:#268BD2;font-weight:normal;font-style:normal}code span.cf,code span.op,code span a.cf,code span a.op{color:#859900;font-weight:normal;font-style:normal}code span.bu,code span.ex,code span a.bu,code span a.ex{color:#657B83;font-weight:normal;font-style:normal}code span.pp,code span a.pp{color:#CB4B16;font-weight:normal;font-style:normal}code span.at,code span a.at{color:#657B83;font-weight:normal;font-style:normal}code span.do,code span.an,code span.cv,code span.in,code span a.do,code span a.an,code span a.cv,code span a.in{color:#93A1A1;font-weight:normal;font-style:italic}a.sourceLine::before{text-decoration:none} diff --git a/src/Scratch/css/solarized.css b/src/Scratch/css/solarized.css new file mode 100644 index 0000000..ee2f1b1 --- /dev/null +++ b/src/Scratch/css/solarized.css @@ -0,0 +1 @@ +.hljs{background:#f3f4f5}pre{padding:.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{color:#dc322f}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} diff --git a/src/Scratch/en/about/index.html b/src/Scratch/en/about/index.html new file mode 100644 index 0000000..151bfb3 --- /dev/null +++ b/src/Scratch/en/about/index.html @@ -0,0 +1,142 @@ + + + + + YBlog - About + + + + + + + + + + + + + + + +
+ + +
+

About

+
+
+
+
+

Avatar

+

@ yann.esposito@gmail.com
+ @yogsototh
+ yogsototh
+ yogsototh
+ yogsototh
+ yogsototh

+
+

ADA: DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp

+

One photo

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

Shortly

+

I am a Senior Software Engineer (Clojurist) for Cisco, Threatgrid team.

+

I was a Machine Learning expert and Software Engineer for Vigiglobe.

+

I tend to use functional programming a lot. Mostly Haskell and Clojure.

+

I am generally passionate about:

+ +

But before all, I love to learn. For example, I learned many programming languages:

+

Functional:

+ +

Object Oriented:

+
    +
  • javascript, C, C++, Objective-C,
  • +
  • Java,
  • +
  • Python,
  • +
  • Ruby,
  • +
+

Also:

+ +

My full resume»

+

Old stuff

+ +
+ +
+
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/01_nanoc/index.html b/src/Scratch/en/blog/01_nanoc/index.html new file mode 100644 index 0000000..065c5de --- /dev/null +++ b/src/Scratch/en/blog/01_nanoc/index.html @@ -0,0 +1,109 @@ + + + + + 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.

+
+
+ + + +
+
+ Published on 2008-10-10 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/02_ackgrep/code/ack b/src/Scratch/en/blog/02_ackgrep/code/ack new file mode 100644 index 0000000..8b80a5e --- /dev/null +++ b/src/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/src/Scratch/en/blog/02_ackgrep/index.html b/src/Scratch/en/blog/02_ackgrep/index.html new file mode 100644 index 0000000..3ec415e --- /dev/null +++ b/src/Scratch/en/blog/02_ackgrep/index.html @@ -0,0 +1,126 @@ + + + + + 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

+ +

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."\n";
+} '
+

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

+
+
+ + + +
+
+ Published on 2009-07-22 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/03_losthighway/index.html b/src/Scratch/en/blog/03_losthighway/index.html new file mode 100644 index 0000000..990b387 --- /dev/null +++ b/src/Scratch/en/blog/03_losthighway/index.html @@ -0,0 +1,189 @@ + + + + + YBlog - A try to demystify 'Lost Highway' + + + + + + + + + + + + + + + + +
+ + +
+

A try to demystify 'Lost Highway'

+

introduction

+ +
+
+
+
+
+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?

+
+
+ + + +
+
+ Published on 2009-08-04 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/04_drm/index.html b/src/Scratch/en/blog/04_drm/index.html new file mode 100644 index 0000000..cdc44ec --- /dev/null +++ b/src/Scratch/en/blog/04_drm/index.html @@ -0,0 +1,118 @@ + + + + + YBlog - DRM are EVIL + + + + + + + + + + + + + + + + +
+ + +
+

DRM are EVIL

+

DRM are bad (+1)

+ +
+
+
+
+

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.

+
+
+ + + +
+
+ Published on 2009-08-15 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/05_git_create_remote_branch/code/git-create-new-branch.sh b/src/Scratch/en/blog/05_git_create_remote_branch/code/git-create-new-branch.sh new file mode 100644 index 0000000..6b36930 --- /dev/null +++ b/src/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/src/Scratch/en/blog/05_git_create_remote_branch/index.html b/src/Scratch/en/blog/05_git_create_remote_branch/index.html new file mode 100644 index 0000000..153c06a --- /dev/null +++ b/src/Scratch/en/blog/05_git_create_remote_branch/index.html @@ -0,0 +1,122 @@ + + + + + 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:

+ +

Of course, I suppose origin is already configured.

+

Edit: Now I use git push -u

+
+
+ + + +
+
+ Published on 2009-08-17 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/06_How_I_use_git/code/git-create-new-branch b/src/Scratch/en/blog/06_How_I_use_git/code/git-create-new-branch new file mode 100644 index 0000000..4ce31d7 --- /dev/null +++ b/src/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/src/Scratch/en/blog/06_How_I_use_git/code/git-get-remote-branches b/src/Scratch/en/blog/06_How_I_use_git/code/git-get-remote-branches new file mode 100644 index 0000000..dd2b3eb --- /dev/null +++ b/src/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/src/Scratch/en/blog/06_How_I_use_git/index.html b/src/Scratch/en/blog/06_How_I_use_git/index.html new file mode 100644 index 0000000..167aaa0 --- /dev/null +++ b/src/Scratch/en/blog/06_How_I_use_git/index.html @@ -0,0 +1,213 @@ + + + + + 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:

+
+ +
+

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:

+
+ +
+

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

+
+ +
+

Now on any computer you can do:

+
+ +
+

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:

+
+ +
+

After that you can do (many times):

+
+ +
+

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

+
+ +
+

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:

+
+ +
+

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:

+
+ +
+
+ +
+
+
+ + + +
+
+ Published on 2009-08-18 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/07_Screensaver_compilation_option_for_Snow_Leopard/index.html b/src/Scratch/en/blog/07_Screensaver_compilation_option_for_Snow_Leopard/index.html new file mode 100644 index 0000000..b4b859c --- /dev/null +++ b/src/Scratch/en/blog/07_Screensaver_compilation_option_for_Snow_Leopard/index.html @@ -0,0 +1,113 @@ + + + + + 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.

+
+
+ + + +
+
+ Published on 2009-09-06 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/code/ssh-443.plist b/src/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/src/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/src/Scratch/en/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/index.html b/src/Scratch/en/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/index.html new file mode 100644 index 0000000..4e79353 --- /dev/null +++ b/src/Scratch/en/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/index.html @@ -0,0 +1,160 @@ + + + + + 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

+
+ +
+

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:

+
+ +
+

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.

+
+
+ + + +
+
+ Published on 2009-09-07 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/09_Why_I_didn-t_keep_whosamung-us/index.html b/src/Scratch/en/blog/09_Why_I_didn-t_keep_whosamung-us/index.html new file mode 100644 index 0000000..55dcffd --- /dev/null +++ b/src/Scratch/en/blog/09_Why_I_didn-t_keep_whosamung-us/index.html @@ -0,0 +1,115 @@ + + + + + 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 +
+
+
+
+ + + +
+
+ Published on 2009-09-11 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/10_Synchronize_Custom_WebSite_with_mobileMe/code/publish b/src/Scratch/en/blog/10_Synchronize_Custom_WebSite_with_mobileMe/code/publish new file mode 100644 index 0000000..0ea3558 --- /dev/null +++ b/src/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/src/Scratch/en/blog/10_Synchronize_Custom_WebSite_with_mobileMe/index.html b/src/Scratch/en/blog/10_Synchronize_Custom_WebSite_with_mobileMe/index.html new file mode 100644 index 0000000..02840ae --- /dev/null +++ b/src/Scratch/en/blog/10_Synchronize_Custom_WebSite_with_mobileMe/index.html @@ -0,0 +1,331 @@ + + + + + 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"
+
+
+
+ + + +
+
+ Published on 2009-09-11 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/11_Load_Disqus_Asynchronously/index.html b/src/Scratch/en/blog/11_Load_Disqus_Asynchronously/index.html new file mode 100644 index 0000000..0b3eaa7 --- /dev/null +++ b/src/Scratch/en/blog/11_Load_Disqus_Asynchronously/index.html @@ -0,0 +1,140 @@ + + + + + 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:

+
+ +
+

by

+
+ +
+

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.

+
+
+ + + +
+
+ Published on 2009-09-17 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/index.html b/src/Scratch/en/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/index.html new file mode 100644 index 0000000..0f7bb50 --- /dev/null +++ b/src/Scratch/en/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/index.html @@ -0,0 +1,127 @@ + + + + + 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:

+
+ +
+

by:

+
+ +
+

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à.

+
+
+ + + +
+
+ Published on 2009-09-28 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2009-09-jQuery-Tag-Cloud/index.html b/src/Scratch/en/blog/2009-09-jQuery-Tag-Cloud/index.html new file mode 100644 index 0000000..2488579 --- /dev/null +++ b/src/Scratch/en/blog/2009-09-jQuery-Tag-Cloud/index.html @@ -0,0 +1,288 @@ + + + + + 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:

+
+ +
+

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:

+
+ +
+

and a div containing links associtated to this tag:

+
+ +
+
+

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.

+
+ +
+

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

+
+ +
+

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

+
+ +
+

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.

+
+ +
+

Finaly a function to generate the XHTML/jQuery code

+
+ +
+

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

+

Of course to be nice you need the associated CSS

+
+ +
+

That’s all folks.

+
+
+ + + +
+
+ Published on 2009-09-23 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2009-09-replace-all-except-some-part/index.html b/src/Scratch/en/blog/2009-09-replace-all-except-some-part/index.html new file mode 100644 index 0000000..0bdefed --- /dev/null +++ b/src/Scratch/en/blog/2009-09-replace-all-except-some-part/index.html @@ -0,0 +1,169 @@ + + + + + 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

+
+ +
+

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

+
+ +
+

An usage is:

+
+ +
+

A better syntax would be:

+
+ +
+

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.

+
+
+ + + +
+
+ Published on 2009-09-22 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/code/publish b/src/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/code/publish new file mode 100644 index 0000000..d028472 --- /dev/null +++ b/src/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/src/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/code/webdav-framework b/src/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/src/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/src/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/index.html b/src/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/index.html new file mode 100644 index 0000000..05f0f22 --- /dev/null +++ b/src/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/index.html @@ -0,0 +1,174 @@ + + + + + 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 – "\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 ! $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 }
+
+

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=( $(diff $localRef $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.yrf $ydestRep/map.yrf # remove the temporary file }

+

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.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 : tsuffix]” 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. +
+
+
+
+ + + +
+
+ Published on 2009-10-28 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2009-10-30-How-to-handle-evil-IE/code/ie.js b/src/Scratch/en/blog/2009-10-30-How-to-handle-evil-IE/code/ie.js new file mode 100644 index 0000000..6b047db --- /dev/null +++ b/src/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/src/Scratch/en/blog/2009-10-30-How-to-handle-evil-IE/index.html b/src/Scratch/en/blog/2009-10-30-How-to-handle-evil-IE/index.html new file mode 100644 index 0000000..88b340f --- /dev/null +++ b/src/Scratch/en/blog/2009-10-30-How-to-handle-evil-IE/index.html @@ -0,0 +1,129 @@ + + + + + YBlog - How to handle evil IE + + + + + + + + + + + + + + + + +
+ + +
+

How to handle evil IE

+ +
+
+
+ +
+ + + +
+
+ Published on 2009-10-30 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2009-10-Focus-vs-Minimalism/index.html b/src/Scratch/en/blog/2009-10-Focus-vs-Minimalism/index.html new file mode 100644 index 0000000..bf4dc57 --- /dev/null +++ b/src/Scratch/en/blog/2009-10-Focus-vs-Minimalism/index.html @@ -0,0 +1,150 @@ + + + + + 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:

+ +

The CSS:

+ +

The javascript code (using jQuery)

+ +

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

+
+
+ + + +
+
+ Published on 2009-10-22 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2009-10-How-to-preload-your-site-with-style/index.html b/src/Scratch/en/blog/2009-10-How-to-preload-your-site-with-style/index.html new file mode 100644 index 0000000..e8f853c --- /dev/null +++ b/src/Scratch/en/blog/2009-10-How-to-preload-your-site-with-style/index.html @@ -0,0 +1,170 @@ + + + + + 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.

+
+ +
+

and here is the associated CSS to #blackpage:

+
+ +
+

and the associated jQuery code:

+
+ +
+

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!

+
+
+ + + +
+
+ Published on 2009-10-03 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2009-10-Wait-to-hide-a-menu-in-jQuery/index.html b/src/Scratch/en/blog/2009-10-Wait-to-hide-a-menu-in-jQuery/index.html new file mode 100644 index 0000000..b2cf4a5 --- /dev/null +++ b/src/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:

+ +

CSS:

+ +

Javascript:

+ +

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

+
+
+ + + +
+
+ Published on 2009-10-26 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2009-10-launch-daemon-from-command-line/index.html b/src/Scratch/en/blog/2009-10-launch-daemon-from-command-line/index.html new file mode 100644 index 0000000..c3c5e4c --- /dev/null +++ b/src/Scratch/en/blog/2009-10-launch-daemon-from-command-line/index.html @@ -0,0 +1,110 @@ + + + + + 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:

+ +

where cmd is your command.

+

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

+
+
+ + + +
+
+ Published on 2009-10-23 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2009-10-untaught-git-usage/index.html b/src/Scratch/en/blog/2009-10-untaught-git-usage/index.html new file mode 100644 index 0000000..8905ec3 --- /dev/null +++ b/src/Scratch/en/blog/2009-10-untaught-git-usage/index.html @@ -0,0 +1,229 @@ + + + + + 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.

+
+
+ + + +
+
+ Published on 2009-10-13 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2009-11-12-Git-for-n00b/code/gitconfig b/src/Scratch/en/blog/2009-11-12-Git-for-n00b/code/gitconfig new file mode 100644 index 0000000..828ac4d --- /dev/null +++ b/src/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/src/Scratch/en/blog/2009-11-12-Git-for-n00b/index.html b/src/Scratch/en/blog/2009-11-12-Git-for-n00b/index.html new file mode 100644 index 0000000..f98c3df --- /dev/null +++ b/src/Scratch/en/blog/2009-11-12-Git-for-n00b/index.html @@ -0,0 +1,468 @@ + + + + + YBlog - Git for n00b + + + + + + + + + + + + + + + + +
+ + +
+

Git for n00b

+

introduction

+ +
+
+
+
+
+

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:

+ +

Everyday Git usage:

+ +

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:

+ +

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:

+
+ +
+

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:

+
+ +
+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:

+ +

Under Mac OS X:

+ + +

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:

+ +

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:

+ +

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:

+ +

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:

+ +

or manually

+ +

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.

+ +

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:

+ +

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:

+ +

Others will be able to get your modifications.

+ +

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

+
+ +
+

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:

+
+ +
+

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

+
+ +
+

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

+ +

send modifications to others

+ +

or more generally

+ +

get back in time

+

For all tree

+ + +

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

+ +

Undo the las merge (if something goes wrong)

+ +

For one file

+ +

list differences between each version

+

list files being modified

+ +

differences between last version files and local files

+ +

differences between some version and local files

+ +

name some version to refer to them in the future

+ +

show historic of modifications

+ +

know who did what and when

+ +

handle conflicts

+ +

manage branches

+

To create a branch:

+ +

To change the current branch:

+ +
+
+ + + +
+
+ Published on 2009-11-12 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2009-12-06-iphone-call-filter/index.html b/src/Scratch/en/blog/2009-12-06-iphone-call-filter/index.html new file mode 100644 index 0000000..72da3b3 --- /dev/null +++ b/src/Scratch/en/blog/2009-12-06-iphone-call-filter/index.html @@ -0,0 +1,107 @@ + + + + + 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

+
+
+ + + +
+
+ Published on 2009-12-06 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2009-12-14-Git-vs--Bzr/code/gitconfig b/src/Scratch/en/blog/2009-12-14-Git-vs--Bzr/code/gitconfig new file mode 100644 index 0000000..78a9edd --- /dev/null +++ b/src/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/src/Scratch/en/blog/2009-12-14-Git-vs--Bzr/index.html b/src/Scratch/en/blog/2009-12-14-Git-vs--Bzr/index.html new file mode 100644 index 0000000..cf7b88a --- /dev/null +++ b/src/Scratch/en/blog/2009-12-14-Git-vs--Bzr/index.html @@ -0,0 +1,167 @@ + + + + + YBlog - Git vs. Bzr + + + + + + + + + + + + + + + + +
+ + +
+

Git vs. Bzr

+

Why I switched from bazaar to git

+ +
+
+
+
+
+

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:

+ +

undo the current modification of the file 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:

+ +

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:

+ +

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

+ +

The git equivalent is far more cryptic:

+ +

One more time, Bazaar is far more readable.

+

Back in time for all the project:

+

with Bazaar:

+ +

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:

+ +

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:

+ +

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:

+ +

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…).

+ +

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.

+ +

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.

+
+
+ + + +
+
+ Published on 2009-12-14 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-01-04-Change-default-shell-on-Mac-OS-X/index.html b/src/Scratch/en/blog/2010-01-04-Change-default-shell-on-Mac-OS-X/index.html new file mode 100644 index 0000000..3bdec6a --- /dev/null +++ b/src/Scratch/en/blog/2010-01-04-Change-default-shell-on-Mac-OS-X/index.html @@ -0,0 +1,109 @@ + + + + + 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 +
+
+
+ + + +
+
+ Published on 2010-01-04 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/code/local.conf b/src/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/src/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/src/Scratch/en/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/index.html b/src/Scratch/en/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/index.html new file mode 100644 index 0000000..e6e4667 --- /dev/null +++ b/src/Scratch/en/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/index.html @@ -0,0 +1,164 @@ + + + + + YBlog - antialias font in Firefox under Ubuntu + + + + + + + + + + + + + + + + +
+ + +
+

antialias font in Firefox under Ubuntu

+ +
+
+
+ +
+ + + +
+
+ Published on 2010-01-12 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-02-15-All-but-something-regexp/index.html b/src/Scratch/en/blog/2010-02-15-All-but-something-regexp/index.html new file mode 100644 index 0000000..5154cf5 --- /dev/null +++ b/src/Scratch/en/blog/2010-02-15-All-but-something-regexp/index.html @@ -0,0 +1,136 @@ + + + + + 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.

+
+
+ + + +
+
+ Published on 2010-02-15 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-02-16-All-but-something-regexp--2-/index.html b/src/Scratch/en/blog/2010-02-16-All-but-something-regexp--2-/index.html new file mode 100644 index 0000000..1232d09 --- /dev/null +++ b/src/Scratch/en/blog/2010-02-16-All-but-something-regexp--2-/index.html @@ -0,0 +1,161 @@ + + + + + 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

+ +

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

+ +

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:

+ +

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:

+ +

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::.

+
+
+ + + +
+
+ Published on 2010-02-16 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-02-18-split-a-file-by-keyword/index.html b/src/Scratch/en/blog/2010-02-18-split-a-file-by-keyword/index.html new file mode 100644 index 0000000..9068a78 --- /dev/null +++ b/src/Scratch/en/blog/2010-02-18-split-a-file-by-keyword/index.html @@ -0,0 +1,117 @@ + + + + + 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} +
+
+
+ + + +
+
+ Published on 2010-02-18 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/code/regex_benchmark_ext.rb b/src/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/src/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/src/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/code/regex_benchmark_strip.rb b/src/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/src/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/src/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/index.html b/src/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/index.html new file mode 100644 index 0000000..da72d81 --- /dev/null +++ b/src/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/index.html @@ -0,0 +1,142 @@ + + + + + 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.

+
+
+ + + +
+
+ Published on 2010-02-23 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-03-22-Git-Tips/index.html b/src/Scratch/en/blog/2010-03-22-Git-Tips/index.html new file mode 100644 index 0000000..ba7ab37 --- /dev/null +++ b/src/Scratch/en/blog/2010-03-22-Git-Tips/index.html @@ -0,0 +1,130 @@ + + + + + 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 $( git br -a ); do case $br in remotes/) print $br ; case ${br:t} in master|HEAD) continue ;; ) git branch –track ${br:t} $br ;; esac ;; esac done
+
+
+
+ + + +
+
+ Published on 2010-03-22 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-03-23-Encapsulate-git/code/eng b/src/Scratch/en/blog/2010-03-23-Encapsulate-git/code/eng new file mode 100644 index 0000000..8dc7396 --- /dev/null +++ b/src/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/src/Scratch/en/blog/2010-03-23-Encapsulate-git/index.html b/src/Scratch/en/blog/2010-03-23-Encapsulate-git/index.html new file mode 100644 index 0000000..36327c8 --- /dev/null +++ b/src/Scratch/en/blog/2010-03-23-Encapsulate-git/index.html @@ -0,0 +1,158 @@ + + + + + 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’ $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
+
+

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.

+
+
+ + + +
+
+ Published on 2010-03-23 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-05-17-at-least-this-blog-revive/index.html b/src/Scratch/en/blog/2010-05-17-at-least-this-blog-revive/index.html new file mode 100644 index 0000000..2b1c39e --- /dev/null +++ b/src/Scratch/en/blog/2010-05-17-at-least-this-blog-revive/index.html @@ -0,0 +1,127 @@ + + + + + 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.

+
+
+ + + +
+
+ Published on 2010-05-17 +
+
+ Follow @yogsototh +
+
+ Yann Esposito© +
+ +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-05-19-How-to-cut-HTML-and-repair-it/code/repair_xml.rb b/src/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/src/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/src/Scratch/en/blog/2010-05-19-How-to-cut-HTML-and-repair-it/index.html b/src/Scratch/en/blog/2010-05-19-How-to-cut-HTML-and-repair-it/index.html new file mode 100644 index 0000000..92e0c39 --- /dev/null +++ b/src/Scratch/en/blog/2010-05-19-How-to-cut-HTML-and-repair-it/index.html @@ -0,0 +1,172 @@ + + + + + YBlog - How to repair a cutted XML? + + + + + + + + + + + + + + + + +

+ + +
+

How to repair a cutted XML?

+

and how to do it without any parsor?

+ +
+
+
+
+

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:

+ +

After the cut, I obtain:

+ +

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:

+ +

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.

+ +

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

+
+
+ + + +
+
+ Published on 2010-05-19 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/code/The_destination_tree.png b/src/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/code/The_destination_tree.png new file mode 100644 index 0000000..243e63d Binary files /dev/null and b/src/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/code/The_destination_tree.png differ diff --git a/src/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/code/The_source_tree.png b/src/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/code/The_source_tree.png new file mode 100644 index 0000000..67dedc9 Binary files /dev/null and b/src/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/code/The_source_tree.png differ diff --git a/src/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/index.html b/src/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/index.html new file mode 100644 index 0000000..4090a8d --- /dev/null +++ b/src/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/index.html @@ -0,0 +1,295 @@ + + + + + YBlog - Trees; Pragmatism and Formalism + + + + + + + + + + + + + + + + +
+ + +
+

Trees; Pragmatism and Formalism

+

When theory is more efficient than practice

+ +
+
+
+
+
+

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:

+ +

and the destination format was in the following general format:

+ +

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:

+

The source tree

+

and

+

The destination tree

+

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:

+ +

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

+ +

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:

+ +

Once adapted to xml it becomes:

+ +

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. +
+
+
+
+ + + +
+
+ Published on 2010-05-24 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-06-14-multi-language-choices/index.html b/src/Scratch/en/blog/2010-06-14-multi-language-choices/index.html new file mode 100644 index 0000000..853e6dd --- /dev/null +++ b/src/Scratch/en/blog/2010-06-14-multi-language-choices/index.html @@ -0,0 +1,131 @@ + + + + + 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.

+
+
+ + + +
+
+ Published on 2010-06-14 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-06-15-Get-my-blog-engine/index.html b/src/Scratch/en/blog/2010-06-15-Get-my-blog-engine/index.html new file mode 100644 index 0000000..a6684ea --- /dev/null +++ b/src/Scratch/en/blog/2010-06-15-Get-my-blog-engine/index.html @@ -0,0 +1,166 @@ + + + + + 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).

+ +

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 ::

+
+
+ + + +
+
+ Published on 2010-06-15 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_hidden.html b/src/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_hidden.html new file mode 100644 index 0000000..946d873 --- /dev/null +++ b/src/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_hidden.html @@ -0,0 +1,20 @@ + + + + + + + + + Hide to analytics + + +
+ + diff --git a/src/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_visible.html b/src/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_visible.html new file mode 100644 index 0000000..0f79658 --- /dev/null +++ b/src/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_visible.html @@ -0,0 +1,20 @@ + + + + + + + + + Hide to analytics + + +
+ + diff --git a/src/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/index.html b/src/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/index.html new file mode 100644 index 0000000..d746cd3 --- /dev/null +++ b/src/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/index.html @@ -0,0 +1,157 @@ + + + + + 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.

+ +

then create two html files. One to hide:

+ +

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

+ +

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.

+
+
+ + + +
+
+ Published on 2010-06-17 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-06-17-track-events-with-google-analytics/code/yga.js b/src/Scratch/en/blog/2010-06-17-track-events-with-google-analytics/code/yga.js new file mode 100644 index 0000000..99f4a46 --- /dev/null +++ b/src/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/src/Scratch/en/blog/2010-06-17-track-events-with-google-analytics/index.html b/src/Scratch/en/blog/2010-06-17-track-events-with-google-analytics/index.html new file mode 100644 index 0000000..2cf3e51 --- /dev/null +++ b/src/Scratch/en/blog/2010-06-17-track-events-with-google-analytics/index.html @@ -0,0 +1,162 @@ + + + + + YBlog - Track Events with Google Analytics + + + + + + + + + + + + + + + + +
+ + +
+

Track Events with Google Analytics

+

Asynchronous Complete Google Analytics with jQuery

+ +
+
+
+
+

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:

+ +

And here is the yga.js file:

+ +

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!

+
+
+ + + +
+
+ Published on 2010-06-17 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-06-19-jQuery-popup-the-easy-way/code/essai.js b/src/Scratch/en/blog/2010-06-19-jQuery-popup-the-easy-way/code/essai.js new file mode 100644 index 0000000..d0c8361 --- /dev/null +++ b/src/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/src/Scratch/en/blog/2010-06-19-jQuery-popup-the-easy-way/index.html b/src/Scratch/en/blog/2010-06-19-jQuery-popup-the-easy-way/index.html new file mode 100644 index 0000000..f70f95f --- /dev/null +++ b/src/Scratch/en/blog/2010-06-19-jQuery-popup-the-easy-way/index.html @@ -0,0 +1,131 @@ + + + + + YBlog - jQuery popup the easy way + + + + + + + + + + + + + + + + +
+ + +
+

jQuery popup the easy way

+ +
+
+
+
+

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

+ +

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.

+
+
+ + + +
+
+ Published on 2010-06-19 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-07-05-Cappuccino-and-Web-applications/index.html b/src/Scratch/en/blog/2010-07-05-Cappuccino-and-Web-applications/index.html new file mode 100644 index 0000000..47996a7 --- /dev/null +++ b/src/Scratch/en/blog/2010-07-05-Cappuccino-and-Web-applications/index.html @@ -0,0 +1,190 @@ + + + + + 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:

+ +

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. +
+
+
+
+ + + +
+
+ Published on 2010-07-05 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-07-07-CSS-rendering-problems-by-navigator/index.html b/src/Scratch/en/blog/2010-07-07-CSS-rendering-problems-by-navigator/index.html new file mode 100644 index 0000000..b98692c --- /dev/null +++ b/src/Scratch/en/blog/2010-07-07-CSS-rendering-problems-by-navigator/index.html @@ -0,0 +1,120 @@ + + + + + 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.

+
+
+ + + +
+
+ Published on 2010-07-07 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-07-09-Indecidabilities/index.html b/src/Scratch/en/blog/2010-07-09-Indecidabilities/index.html new file mode 100644 index 0000000..71740e7 --- /dev/null +++ b/src/Scratch/en/blog/2010-07-09-Indecidabilities/index.html @@ -0,0 +1,201 @@ + + + + + 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. +
+
+
+
+ + + +
+
+ Published on 2010-08-11 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-07-31-New-style-after-holidays/index.html b/src/Scratch/en/blog/2010-07-31-New-style-after-holidays/index.html new file mode 100644 index 0000000..2ac5169 --- /dev/null +++ b/src/Scratch/en/blog/2010-07-31-New-style-after-holidays/index.html @@ -0,0 +1,107 @@ + + + + + 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.

+
+
+ + + +
+
+ Published on 2010-07-31 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-08-23-Now-heberged-on-heroku/code/config.ru b/src/Scratch/en/blog/2010-08-23-Now-heberged-on-heroku/code/config.ru new file mode 100644 index 0000000..48ae352 --- /dev/null +++ b/src/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/src/Scratch/en/blog/2010-08-23-Now-heberged-on-heroku/index.html b/src/Scratch/en/blog/2010-08-23-Now-heberged-on-heroku/index.html new file mode 100644 index 0000000..a37f3a8 --- /dev/null +++ b/src/Scratch/en/blog/2010-08-23-Now-heberged-on-heroku/index.html @@ -0,0 +1,168 @@ + + + + + YBlog - Now hosted by heroku + + + + + + + + + + + + + + + + +
+ + +
+

Now hosted by heroku

+

Host static website on 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:

+ +

and the .gems file needed to install rack middlewares.

+ +

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

+ +

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. +
+
+
+
+ + + +
+
+ Published on 2010-08-23 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-08-31-send-mail-from-command-line-with-attached-file/index.html b/src/Scratch/en/blog/2010-08-31-send-mail-from-command-line-with-attached-file/index.html new file mode 100644 index 0000000..281f000 --- /dev/null +++ b/src/Scratch/en/blog/2010-08-31-send-mail-from-command-line-with-attached-file/index.html @@ -0,0 +1,142 @@ + + + + + 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

+ +

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
+
+Not really readable.
+After some research I found the solution.
+Use MIME instead of `uuencode`.
+
+Finally I made it manually using `sendmail`.
+I didn" t dare to use `telnet`. The command to use is: ~~~~~~ {.zsh} sendmail -t -oi < mailcontent.txt ~~~~~~ Of course you need to create the `mailcontent.txt` file. It should contains: 
+From: from@mail.com
+To: to@mail.com
+Subject: View the attached file
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="-"
+
+This is a MIME encoded message. Decode it with "Decoder"
+or any other MIME reading software. Decoder is available
+at .
+---
+Content-Type: image/jpeg; name="fic.jpg"
+Content-Transfer-Encoding: base64
+Content-Disposition: inline; filename="fic.jpg"
+
+H4sICB6Ke0wAA2Rjcl93aXRob3V0X2tleXdvcmQuY3N2ANSdW5ubOJPH7/e7
+7Brw+dmrTk8yk7yTSTaZeWd2b/TIIGy6MRAE7ng+/VaJgwF3g522SsxN2+3T
+/4eOJamqmARP+yibvI8ykUYim+x5EE2euBfIyd3byZ+fvvzr7svbu8ndTx/f
+...
+
+

And to obtain the “encoded” file in base64 I used:

+

uuencode -m fic.jpg fic.jpg ~~~~~~

+

That is all. Sometimes technology is so easy to use. If I need it another time I should consider to make a shell script to automatize this.

+
+
+ + + +
+
+ Published on 2010-08-31 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/code/gitmtime.rb b/src/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/src/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/src/Scratch/en/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/index.html b/src/Scratch/en/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/index.html new file mode 100644 index 0000000..bcbf195 --- /dev/null +++ b/src/Scratch/en/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/index.html @@ -0,0 +1,123 @@ + + + + + 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:

+ +

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.

+
+
+ + + +
+
+ Published on 2010-09-02 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-09-02-base64-and-sha1-on-iPhone/code/iphone_base64_sha1.c b/src/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/src/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:

+ +

The base64 algorithm must be programmed by hand on iPhone!

+ +
+
+ + + +
+
+ Published on 2010-09-02 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-10-06-New-Blog-Design-Constraints/index.html b/src/Scratch/en/blog/2010-10-06-New-Blog-Design-Constraints/index.html new file mode 100644 index 0000000..c2de447 --- /dev/null +++ b/src/Scratch/en/blog/2010-10-06-New-Blog-Design-Constraints/index.html @@ -0,0 +1,112 @@ + + + + + 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.

+
+
+ + + +
+
+ Published on 2010-10-06 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/index.html b/src/Scratch/en/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/index.html new file mode 100644 index 0000000..296e6ce --- /dev/null +++ b/src/Scratch/en/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/index.html @@ -0,0 +1,134 @@ + + + + + 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.

+
+
+ + + +
+
+ Published on 2010-10-10 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.c b/src/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.c new file mode 100644 index 0000000..aa39828 --- /dev/null +++ b/src/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/src/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.py b/src/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.py new file mode 100644 index 0000000..c0d4001 --- /dev/null +++ b/src/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/src/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.rb b/src/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum.rb new file mode 100644 index 0000000..aa3c906 --- /dev/null +++ b/src/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/src/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum2.c b/src/Scratch/en/blog/2010-10-14-Fun-with-wav/code/wavsum2.c new file mode 100644 index 0000000..48b7138 --- /dev/null +++ b/src/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/src/Scratch/en/blog/2010-10-14-Fun-with-wav/index.html b/src/Scratch/en/blog/2010-10-14-Fun-with-wav/index.html new file mode 100644 index 0000000..35f6805 --- /dev/null +++ b/src/Scratch/en/blog/2010-10-14-Fun-with-wav/index.html @@ -0,0 +1,351 @@ + + + + + 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.

+ +

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:

+ +

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:

+ +

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

+ +

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:

+ +

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:

+ +

and luikore proposed an impressive Ruby version:

+ +
+
+ + + +
+
+ Published on 2010-10-14 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2010-10-26-LaTeX-like-macro-and-markdown/code/macros.rb b/src/Scratch/en/blog/2010-10-26-LaTeX-like-macro-and-markdown/code/macros.rb new file mode 100644 index 0000000..17ebf55 --- /dev/null +++ b/src/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/src/Scratch/en/blog/2010-10-26-LaTeX-like-macro-and-markdown/index.html b/src/Scratch/en/blog/2010-10-26-LaTeX-like-macro-and-markdown/index.html new file mode 100644 index 0000000..feb06d6 --- /dev/null +++ b/src/Scratch/en/blog/2010-10-26-LaTeX-like-macro-and-markdown/index.html @@ -0,0 +1,160 @@ + + + + + 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.

+ +

Macros could be very useful, read this article for example.

+
+
+ + + +
+
+ Published on 2010-10-26 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2011-01-03-Happy-New-Year/index.html b/src/Scratch/en/blog/2011-01-03-Happy-New-Year/index.html new file mode 100644 index 0000000..86a8555 --- /dev/null +++ b/src/Scratch/en/blog/2011-01-03-Happy-New-Year/index.html @@ -0,0 +1,109 @@ + + + + + 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!

+
+
+ + + +
+
+ Published on 2011-01-01 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/index.html b/src/Scratch/en/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/index.html new file mode 100644 index 0000000..b541469 --- /dev/null +++ b/src/Scratch/en/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/index.html @@ -0,0 +1,179 @@ + + + + + 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.

+ +

The only way to handle this is to make the following code:

+ +

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. +
+
+
+
+ + + +
+
+ Published on 2011-01-03 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/2011-04-20-Now-hosted-on-github/index.html b/src/Scratch/en/blog/2011-04-20-Now-hosted-on-github/index.html new file mode 100644 index 0000000..8a5c827 --- /dev/null +++ b/src/Scratch/en/blog/2011-04-20-Now-hosted-on-github/index.html @@ -0,0 +1,108 @@ + + + + + YBlog - Now hosted on github + + + + + + + + + + + + + + + +
+ + +
+

Now hosted on github

+ +
+
+
+
+
+Title image +
+

I am now hosted on github.

+
+
+ + + +
+
+ Published on 2011-04-20 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/A-more-convenient-diff/code/ydiff b/src/Scratch/en/blog/A-more-convenient-diff/code/ydiff new file mode 100644 index 0000000..90d6c85 --- /dev/null +++ b/src/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/src/Scratch/en/blog/A-more-convenient-diff/index.html b/src/Scratch/en/blog/A-more-convenient-diff/index.html new file mode 100644 index 0000000..e03409c --- /dev/null +++ b/src/Scratch/en/blog/A-more-convenient-diff/index.html @@ -0,0 +1,130 @@ + + + + + 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.

+ +
+
+ + + +
+
+ Published on 2011-08-17 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Category-Theory-Presentation/index.html b/src/Scratch/en/blog/Category-Theory-Presentation/index.html new file mode 100644 index 0000000..e35ccaa --- /dev/null +++ b/src/Scratch/en/blog/Category-Theory-Presentation/index.html @@ -0,0 +1,1068 @@ + + + + + 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.

+
+ +
+
+ + + +
+
+ Published on 2012-12-12 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Hakyll-setup/index.html b/src/Scratch/en/blog/Hakyll-setup/index.html new file mode 100644 index 0000000..ae8490a --- /dev/null +++ b/src/Scratch/en/blog/Hakyll-setup/index.html @@ -0,0 +1,520 @@ + + + + + YBlog - Hakyll setup + + + + + + + + + + + + + + + + +
+ + +
+

Hakyll setup

+ +
+
+
+
+
+Main image +
+
+

tl;dr: How I use hakyll. Abbreviations, typography corrections, multi-language, use index.html, etc…

+
+

This website is done with Hakyll.

+

Hakyll can be considered as a minimal cms. But more generally it is a library helping file generation. We can view it as an advanced build system (like make).

+

From the user perspective I blog this way:

+
    +
  1. I open an editor (vim in my case) and edit a markdown file. It looks like this
  2. +
+ +
    +
  1. I open a browser and reload time to time to see the change.
  2. +
  3. Once I finished I’ve written a very minimal script which mainly do a git push. My blog is hosted on github.
  4. +
+

Being short sighted one could reduce the role of Hakyll to:

+
+

create (resp. update) html file when I create (resp. change) a markdown file.

+
+

While it sounds easy, there are a lot of hidden details:

+
    +
  • Add metadatas like keywords.
  • +
  • Create an archive page containing a list of all the posts.
  • +
  • Deal with static files.
  • +
  • Creating an rss feed.
  • +
  • Filter the content with some function.
  • +
  • Dealing with dependencies.
  • +
+

The work of Hakyll is to help you with these. But let’s start with the basic concepts.

+

The concepts and syntax

+
+Overview +
+

For each file you create, you have to provide:

+
    +
  • a destination path
  • +
  • a list of content filters.
  • +
+

First, let’s start with the simplest case: static files (images, fonts, etc…). Generally, you have a source directory (here is the current directory) and a destination directory _site.

+

The Hakyll code is:

+ +

This program will copy static/foo.jpg to _site/static/foo.jpg. I concede this is a bit overkill for a simple cp. Now how to write a markdown file and generate an html one?

+ +

If you create a file posts/foo.md, it will create a file _site/posts/foo.html.

+

If the file posts/foo.md contains

+ +

the file _site/posts/foo.html, will contain

+ +

But horror! _site/posts/cthulhu.html is not a complete html file. It doesn’t have any header nor footer, etc… This is where you use templates. I simply add a new directive in the compile block.

+ +

Now if templates/posts.html contains:

+ +

our cthulhu.html contains (indentation added for readability):

+ +

See, it’s easy But we have a problem. How could we change the title or add keywords?

+

The solution is to use Contexts. For this, we first need to add some metadatas to our markdown1.

+ +

And modify slightly our template:

+ +

As Sir Robin said just before dying before the Bridge of Death:

+
+

“That’s EASY!”

+

Sir Robin, the Not-Quite-So-Brave-As-Sir-Lancelot

+
+

Real customization

+

Now that we understand the basic functionality. How to:

+
    +
  • use SASS?
  • +
  • add keywords?
  • +
  • simplify url?
  • +
  • create an archive page?
  • +
  • create an rss feed?
  • +
  • filter the content?
  • +
  • add abbreviations support?
  • +
  • manage two languages?
  • +
+

Use SASS

+

That’s easy. Simply call the executable using unixFilter. Of course you’ll have to install SASS (gem install sass). And we also use compressCss to gain some space.

+ +

Add keywords

+

In order to help to reference your website on the web, it is nice to add some keywords as meta datas to your html page.

+ +

In order to add keywords, we could not directly use the markdown metadatas. Because, without any, there should be any meta tag in the html.

+

An easy answer is to create a Context that will contains the meta tag.

+ +

Then we pass this Context to the loadAndApplyTemplate function:

+ +
+

☞ Here are the imports I use for this tutorial.

+ +
+

Simplify url

+

What I mean is to use url of the form:

+
http://domain.name/post/title-of-the-post/
+

I prefer this than having to add file with .html extension. We have to change the default Hakyll route behavior. We create another function niceRoute.

+ +

Not too difficult. But! There might be a problem. What if there is a foo/index.html link instead of a clean foo/ in some content?

+

Very simple, we simply remove all /index.html to all our links.

+ +

And we apply this filter at the end of our compilation

+ +

Create an archive page

+

Creating an archive start to be difficult. There is an example in the default Hakyll example. Unfortunately, it assumes all posts prefix their name with a date like in 2013-03-20-My-New-Post.md.

+

I migrated from an older blog and didn’t want to change my url. Also I prefer not to use any filename convention. Therefore, I add the date information in the metadata published. And the solution is here:

+ +

Where templates/archive.html contains

+ +

And base.html is a standard template (simpler than post.html).

+

archiveCtx provide a context containing an html representation of a list of posts in the metadata named posts. It will be used in the templates/archive.html file with $posts$.

+ +

postList returns an html representation of a list of posts given an Item sort function. The representation will apply a minimal template on all posts. Then it concatenate all the results. The template is post-item.html:

+ +

Here is how it is done:

+ +

createdFirst sort a list of item and put it inside Compiler context. We need to be in the Compiler context to access metadatas.

+ +

It wasn’t so easy. But it works pretty well.

+

Create an rss feed

+

To create an rss feed, we have to:

+
    +
  • select only the lasts posts.
  • +
  • generate partially rendered posts (no css, js, etc…)
  • +
+

We could then render the posts twice. One for html rendering and another time for rss. Remark we need to generate the rss version to create the html one.

+

One of the great feature of Hakyll is to be able to save snapshots. Here is how:

+ +

Now for each post there is a snapshot named “content” associated. The snapshots are created before applying a template and after applying pandoc. Furthermore feed don’t need a source markdown file. Then we create a new file from no one. Instead of using match, we use create:

+ +

The feedConfiguration contains some general informations about the feed.

+ +

Great idea certainly steal from nanoc (my previous blog engine)!

+

Filter the content

+

As I just said, nanoc was my preceding blog engine. It is written in Ruby and as Hakyll, it is quite awesome. And one thing Ruby does more naturally than Haskell is regular expressions. I had a lot of filters in nanoc. I lost some because I don’t use them much. But I wanted to keep some. Generally, filtering the content is just a way to apply to the body a function of type String -> String.

+

Also we generally want prefilters (to filter the markdown) and postfilters (to filter the html after the pandoc compilation).

+

Here is how I do it:

+ +

Where

+ +

Now we have a simple way to filter the content. Let’s augment the markdown ability.

+

Add abbreviations support

+

Comparing to LaTeX, a very annoying markdown limitation is the lack of abbreviations.

+

Fortunately we can filter our content. And here is the filter I use:

+ +

It will search for all string starting by ‘%’ and it will search in the Map if there is a corresponding abbreviation. If there is one, we replace the content. Otherwise we do nothing.

+

Do you really believe I type

+ +

each time I write LaTeX?

+

Manage two languages

+

Generally I write my post in English and French. And this is more difficult than it appears. For example, I need to filter the language in order to get the right list of posts. I also use some words in the templates and I want them to be translated.

+ +

First I create a Map containing all translations.

+ +

Then I create a context for all key:

+ +

Conclusion

+

The full code is here. And except from the main file, I use literate Haskell. This way the code should be easier to understand.

+

If you want to know why I switched from nanoc:

+

My preceding nanoc website was a bit too messy. So much in fact, that the dependency system recompiled the entire website for any change.

+

So I had to do something about it. I had two choices:

+
    +
  1. Correct my old code (in Ruby)
  2. +
  3. Duplicate the core functionalities with Hakyll (in Haskell)
  4. +
+

I added too much functionalities in my nanoc system. Starting from scratch (almost) remove efficiently a lot of unused crap.

+

So far I am very happy with the switch. A complete build is about 4x faster. I didn’t broke the dependency system this time. As soon as I modify and save the markdown source, I can reload the page in the browser.

+

I removed a lot of feature thought. Some of them will be difficult to achieve with Hakyll. A typical example:

+

In nanoc I could take a file like this as source:

+ +

And it will create a file foo.hs which could then be downloaded.

+ +
+
+
    +
  1. We could also add the metadatas in an external file (foo.md.metadata).

  2. +
+
+
+
+ + + +
+
+ Published on 2013-03-16 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Haskell-Mandelbrot/code/animandel.hs b/src/Scratch/en/blog/Haskell-Mandelbrot/code/animandel.hs new file mode 100644 index 0000000..6a81748 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-Mandelbrot/index.html b/src/Scratch/en/blog/Haskell-Mandelbrot/index.html new file mode 100644 index 0000000..7b826f5 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-Mandelbrot/index.html @@ -0,0 +1,205 @@ + + + + + YBlog - ASCII Haskell Mandelbrot + + + + + + + + + + + + + + + + +
+ + +
+

ASCII Haskell Mandelbrot

+ +
+
+
+
+

Here is the obfuscated code:

+ +

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)
+
+
+ + + +
+
+ Published on 2011-07-10 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/00_Introduction.lhs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/00_Introduction.lhs new file mode 100644 index 0000000..1d9df7d --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/01_Introduction/hglmandel.lhs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/01_Introduction/hglmandel.lhs new file mode 100644 index 0000000..954c6f8 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/02_Edges/HGLMandelEdge.lhs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/02_Edges/HGLMandelEdge.lhs new file mode 100644 index 0000000..715db44 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/03_Mandelbulb/Mandelbulb.lhs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/03_Mandelbulb/Mandelbulb.lhs new file mode 100644 index 0000000..fc20dc0 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/ExtComplex.hs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/ExtComplex.hs new file mode 100644 index 0000000..caba8d0 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandel.hs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandel.hs new file mode 100644 index 0000000..9500e0b --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandelbulb.lhs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandelbulb.lhs new file mode 100644 index 0000000..d9fb813 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/YBoiler.hs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/YBoiler.hs new file mode 100644 index 0000000..867f8b7 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/ExtComplex.hs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/ExtComplex.hs new file mode 100644 index 0000000..caba8d0 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandel.hs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandel.hs new file mode 100644 index 0000000..7c1ef34 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandelbulb.lhs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandelbulb.lhs new file mode 100644 index 0000000..f58abff --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/YGL.hs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/YGL.hs new file mode 100644 index 0000000..2f6e166 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/ExtComplex.hs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/ExtComplex.hs new file mode 100644 index 0000000..caba8d0 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandel.hs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandel.hs new file mode 100644 index 0000000..7c1ef34 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandelbulb.lhs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandelbulb.lhs new file mode 100644 index 0000000..90b9e04 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/YGL.hs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/YGL.hs new file mode 100644 index 0000000..ee70ae7 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/07_Conclusion/Conclusion.lhs b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/07_Conclusion/Conclusion.lhs new file mode 100644 index 0000000..d222d3f --- /dev/null +++ b/src/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/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/index.html b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/index.html new file mode 100644 index 0000000..72dc199 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/index.html @@ -0,0 +1,1240 @@ + + + + + YBlog - Haskell Progressive Example + + + + + + + + + + + + + + + + +
+ + +
+

Haskell Progressive Example

+

An OpenGL 3D extension of the Mandelbrot set

+ +
+
+
+
+
+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

+ +

For efficiency reason3, I will not use the default Haskell Complex data type.

+ + +

We declare some useful functions for manipulating complex numbers:

+ +

Let us start

+

We start by giving the main architecture of our program:

+ +

Mainly, we initialize our OpenGL application. We declared that the function display will be used to render the graphics:

+ +

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:

+ +

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.

+ +

And of course our list of colored points. In OpenGL the default coordinate are from -1 to 1.

+ +

We need a function which transform an integer value to some color:

+ +

And now the mandel function. Given two coordinates in pixels, it returns some integer value:

+ +

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.

+ +

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

+
+ +
+

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

+ +

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.

+ +

We only need to compute the positive point. The Mandelbrot set is symmetric relatively to the abscisse axis.

+ +

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:

+ +

No rocket science here. See the result now:

+
+The edges of the mandelbrot set +
+ +

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:

+
+ +
+ +

We declare a new type ExtComplex (for extended complex). An extension of complex numbers with a third component:

+ +

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.

+ +

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.

+ +

The idle is here to change the states. There should never be any modification done in the display function.

+ +

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)

+ +

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).

+ +

Note display takes some parameters this time. This function if full of boilerplate:

+ +

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.

+ +

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.

+ +

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.

+ +

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:

+ +

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.

+ +

The rest of the program is very close to the preceding one.

+
+ +

I made the color slightly brighter

+ +

We only changed from Complex to ExtComplex of the main f function.

+ +
+

We simply add a new dimension to the mandel function and change the type signature of f from Complex to ExtComplex.

+ +

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.

+ + +

The yMainLoop takes two arguments: the title of the window and a function from time to triangles

+ +

We set some global constant (this is generally bad).

+ +

We then generate colored points from our function. This is similar to the preceding section.

+ + +

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:

    +
      +
    • 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. +
  3. 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.

  4. +
+

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.

+ +

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.

+ +

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.

+ +

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.

+ +

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.

+ +

Note (-*<) is the scalar product (α -*< (x,y,z) = (αx,αy,αz)). Also note we could add two Point3D.

+ +

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
  • +
+ +

Here is our initial world state.

+ +

We will define shapeFunc later. Here is the function which transform the world even without user action. Mainly it makes some rotation.

+ +

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).

+ +

With the color function.

+ +

The rest is similar to the preceding sections.

+ +

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.

+ + + + +

Our initial world state is slightly changed:

+ +

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.

+ +

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.

+ +

All the rest is exactly the same.

+
+ +
+

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. +
+
+
+
+ + + +
+
+ Published on 2012-06-15 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Haskell-Tutorials--a-tutorial/index.html b/src/Scratch/en/blog/Haskell-Tutorials--a-tutorial/index.html new file mode 100644 index 0000000..3efb6d8 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-Tutorials--a-tutorial/index.html @@ -0,0 +1,365 @@ + + + + + YBlog - Haskell Tutorials, a tutorial + + + + + + + + + + + + + + + + +
+ + +
+

Haskell Tutorials, a tutorial

+ +
+
+
+
+
+Main image +
+
+

tl;dr: Some hints on how to make great documentation for Haskell libraries.

+
    +
  1. Create a Tutorial module containing nothing except documentation.
  2. +
  3. Mention the Tutorial module in your cabal description
  4. +
  5. Use doctest to check your documentation is up to date
  6. +
  7. For more complex real world examples, link to the source of some test.
  8. +
+
+

Great documentation make a big difference. A bad documentation could simply make people not using your lib.

+

My friend was learning Haskell. To start he tried a Haskell library to make a small application. The documentation was deprecated to the point he wasn’t able to make a basic example work. How do you believe he felt? What does he thought about Haskell in general?

+

So here are my hint on how to make a great documentation in Haskell.

+

Documentation can take many different form.

+
    +
  1. Tutorials/Guides – write some prose which friendly take a user by hand and help him
  2. +
  3. Examples – how to use each function
  4. +
  5. Generated API Documentation – haddock
  6. +
+

Hints

+

Tutorials/Guides

+
    +
  1. Create a new module named Tutorial (or Guide.GuideTopic)
  2. +
  3. Create a link to the tutorial in the cabal description
  4. +
  5. Create a link to the tutorial in your README
  6. +
  7. Here is an example some Tutorial module content:
  8. +
+ +

To prevent obsolescence of your tutorial, use doctest.

+

That way when you’ll do a stack test or cabal test you’ll get errors if some example doesn’t work anymore.

+

Examples (doctest)

+

doctest is a great way to provide examples in your code documentation. These example will then be used as tests. Apparently it comes from Python community.

+

To use doctest, this is very simple:

+ +

And to make it works simply verify you have a test bloc in your .cabal file looking like this:

+
test-suite doctest
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test
+  main-is: DocTest.hs
+  build-depend: base >= 4.7 && < 5
+              , <YOUR_LIBRARY> 
+              , Glob >= 0.7
+              , doctest >= 0.9.12
+

and in test/DocTest.hs simply use

+ +

Now stack test or cabal test will check the validity of your documentation.

+

Bonuses

+

Verifying documentation coverage

+
    +
  1. Install haddock stack install haddock or cabal install haddock
  2. +
  3. Launch haddock without output format:
  4. +
+
> haddock src/**/*.hs
+Haddock coverage:
+ 100% ( 15 / 15) in 'Data.Duration'
+ 100% (  3 /  3) in 'Data.Duration.Tutorial'
+

Continuous Integration

+

There are plenty of alternative solution. I provide the one I believe would be used by most people. So if you use github simply create an account on travis.

+

Add a .travis.yml file in your repo containing the content of the file here and remove the builds you don’t need. It will build your project using a lot of different GHC versions and environemnts.

+

If you are afraid by such its complexity you might just want to use this one:

+ +

Don’t forget to activate your repo in travis.

+

For some bonus points add the build status badge in your README.md file:

+ +

Congratulation! Now if you break your documentation examples, you’ll get notified.

+

Badges

+

You could add badges to your README.md file.

+

Here is a list of some: shields.io

+

Hackage

+ +

Stackage

+

If you didn’t declared your package to stackage, please do it. It isn’t much work. Just edit a file to add your package. And you’ll could be able to add another badge:

+ +

See Stackage Badges for more informations.

+

Creating a new project with stack

+

If you use stack I suggest you to use the tasty-travis template. It will include the boilerplate for:

+
    +
  • tests
  • +
  • doctest
  • +
  • benchmark
  • +
  • travis CI
  • +
  • a README file to help you start
  • +
+

So edit your ~/.stack/config.yaml like this:

+
templates:
+  params:
+      author-name: Your Name
+      author-email: your@mail.com
+      copyright: 'Copyright: (c) 2016 Your Name'
+      github-username: yourusername
+      category: Development
+

And then you can create a new projec with:

+
stack new my-project tasty-travis
+

Generated Documentation

+

Even not doing anything, if you submit your library to hackage, haddock should generate some API documentation for free.

+

But to make real documentation you need to add some manual annotations.

+

Functions:

+
-- | My function description
+myFunction :: T1 -- ^ arg1 description
+           -> T2 -- ^ arg2 description
+myFunction arg1 arg2 = ...
+

Data:

+
data MyData a b
+  = C1 a b -- ^ doc for constructor C1
+  | C2 a b -- ^ doc for constructor C2
+
+data MyData a b
+  = C { a :: TypeA -- ^ field a description
+      , b :: TypeB -- ^ field b description
+      }
+

Module:

+
{-|
+Module    : MyModule
+Description: Short description
+Copyright : (c)
+License : MIT
+
+Here is a longer description of this module.
+With some code symbol @MyType@.
+And also a block of code:
+
+@
+data MyData = C Int Int
+
+myFunction :: MyData -> Int
+@
+
+-}
+

Documentation Structure:

+
module MyModule (
+  -- * Classes
+  C(..),
+  -- * Types
+  -- ** A data type
+  T,
+  -- ** A record
+  R,
+  -- * Some functions
+  f, g
+  ) where
+

That will generate headings.

+

Other Random Ideas

+

In Haskell we have great tools like hayoo! and hoogle.

+

And hackage and stackage provide also a lot of informations.

+

But generally we lack a lot of Tutorials and Guides. This post was an attempt to help people making more of them.

+

But there are other good ideas to help improve the situation.

+ +

In clojure when you create a new project using lein new my-project a directory doc is created for you. It contains a file with a link to this blog post:

+ +

Having a page by function/symbol with comments

+

If you try to search for some clojure function on a search engine there is a big chance the first result will link to:

+
    +
  • clojuredocs.org: try to search for reduce, update-in or index for example
  • +
+

For each symbol necessiting a documentation. You don’t only have the details and standard documentation. You’ll also get:

+
    +
  • Responsive Design (sometime you want to look at documentation on a mobile)
  • +
  • Contributed Examples
  • +
  • Contributed See Also section
  • +
  • Contributed notes/comments
  • +
+

clojuredocs.org is an independant website from the official Clojure website.

+

Most of the time, if you google the function you search you end up on clojuredocs for wich there are many contributions.

+

Currently stackage is closer to these feature than hackage. Because on stackage you have access to the README and also some comments by package.

+

I believe it would be more efficient to have at least a page by module and why not a page by symbol (data, functions, typeclasses…).

+

For example, we could provide details about foldl for example. Also as there would be less information to display, it will make the design cleaner.

+

Today, if you want to help documenting, you need to make a PR to the source of some library. While if we had an equivalent to clojuredocs for Haskell, adding documentation would simply be a few clicks away:

+
    +
  1. login
  2. +
  3. add/edit some example, comments, see-also section
  4. +
+

There are more than 23k people on /r/haskell. If only 1% of them would take 10 minutes adding a bit of documentation it will certainly change a lot of things in the percieved documentation quality.

+

And last but not least,

+

Design is important

+
+Design is Important +
+

Design is a vague word. A good design should care not only about how something look, but also how users will interact with it. For example by removing things to focus on the essential.

+

When I stumble upon some random blog post or random specification in the Haskell community, I had too much a feeling of old fashioned design.

+

If you look at node.js community lot of their web page look cleaner, easier to read and in the end, more user friendly.

+

Haskell is very different from node, I wouldn’t like to replace all long and precise documentation with short human unprecise concepts. I don’t want to transform scientific papers by tweets.

+

But like the scientific community has upgraded with the use of LaTeX, I believe we could find something similar that would make, very clean environment for most of us. A kind of look and feel that will be

+
    +
  • modern
  • +
  • device friendly (either on computer, mobile, tablet)
  • +
  • efficient, focus on what is most important and is helpful
  • +
+
+
+ + + +
+
+ Published on 2016-05-06 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/00_preamble.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/00_preamble.lhs new file mode 100644 index 0000000..ae51c26 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/00_preamble.lhs @@ -0,0 +1,124 @@ +begindiv(intro) + +en: I really believe all developers should learn Haskell. +en: I don't think everyone needs to be super Haskell ninjas, +en: but they should at least discover what Haskell has to offer. +en: Learning Haskell opens 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 fondements : + +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: The 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 parler 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 fut 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 entre autres 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: D'abord ["Learn You a Haskell"](http://haskell.fr/lyah/) +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: In contrast, this article is a very brief and dense overview of all major aspects of Haskell. +en: I also added some information I lacked while I learned Haskell. +fr: Cet article fait un résumé très dense et rapide des aspects 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 [github](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 see a separator with a filename ending in `.lhs` +en: > you can 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: > Certains ne marcheront pas, mais la majorité vous donneront un résultat. +fr: > Vous devriez voir un lien juste en dessous. + +enddiv diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/00_hello_world.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/00_hello_world.lhs new file mode 100644 index 0000000..d1ddeed --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/00_hello_world.lhs @@ -0,0 +1,76 @@ +

Introduction

+ +en:

Install

+fr:

Installation

+ +blogimage("Haskell-logo.png", "Haskell logo") + +en: There are different way to install Haskell, I would recommend to use +fr: Aujourd'huil je considère que la manière la plus aisée d'installer Haskell est d'utiliser +[`stack`](https://haskellstack.org). + +en: There are other way to install Haskell on your system you could visit, +en: you can learn more about it by visiting +fr: Il y a d'autres maniètres d'installer Haskell sur votre system, +fr: vous pouvez en savoir plus en visitant +[haskell.org](https://haskell.org) +en: or +fr: ou +[haskell-lang.org](https://haskell-lang.org) + +en: Tools: +fr: Outils: + +en: - `ghc`: Compiler similar to gcc for `C`. +en: - `ghci`: Interactive Haskell (REPL) +en: - `runhaskell`: Execute a program without compiling it. Convenient but very slow compared to compiled programs. +fr: - `ghc`: Compilateur similaire à gcc pour le langage `C`. +fr: - `ghci`: Console Haskell interactive (Read-Eval-Print Loop) +fr: - `runhaskell`: Exécuter un programme sans le compiler. Pratique mais très lent comparé aux programmes compilés. + +en:

Don't be afraid

+fr:

Ne soyez pas effrayés!

+ +blogimage("munch_TheScream.jpg","The Scream") + +en: Many books/articles about Haskell start by introducing some esoteric formula (quick sort, Fibonacci, etc...). +en: I will do the exact opposite. +en: At first I won't show you any Haskell super power. +en: I will start with similarities between Haskell and other programming languages. +en: Let's jump to the mandatory "Hello World". +fr: Beaucoup de livres/articles sur Haskell commencent par présenter des formules ésotériques (Algorithmes de tri rapide, suite de Fibonacci, etc...). +fr: Je ferai l'exact opposé +fr: En premier lieu je ne vous montrerai pas les super-pouvoirs d'Haskell. +fr: Je vais commencer par les similarités avec les autres langages de programmation. +fr: Commençons par l'indispensable "Hello World!". + +> main = putStrLn "Hello World!" + +en: To run it, you can save this code in a `hello.hs` and: +fr: Pour l'exécuter, vous pouvez enregistrer ce code dans un fichier `hello.hs` et: + + +~ runhaskell ./hello.hs +Hello World! + + +en: or if you use `stack` first run `stack setup` and then: +fr: ou si vous utilisez `stack` lancez d'abord `stack setup` et ensuite : + + +~ stack runhaskell ./hello.hs +Hello World! + + + +en: You could also download the literate Haskell source. +en: You should see a link just above the introduction title. +en: Download this file as `00_hello_world.lhs` and: +fr: Vous pouvez également télécharger la source Haskell littérale. +fr: Vous devriez voir un lien juste au dessus du titre de l'introduction. +fr: Téléchargez ce fichier en tant que `00_hello_world.lhs` et: + + +~ runhaskell 00_hello_world.lhs +Hello World! + diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/10_hello_you.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/10_hello_you.lhs new file mode 100644 index 0000000..ec5ca13 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/10_hello_you.lhs @@ -0,0 +1,52 @@ +en: Now, a program asking your name and replying "Hello" using the name you entered: +fr: Maintenant, un programme qui demande votre nom et répond "Hello" suivit du nom que vous avez entré: + +> main = do +> print "What is your name?" +> name <- getLine +> print ("Hello " ++ name ++ "!") + +en: First, let us compare this with similar programs in a few imperative languages: +fr: Premièrement, comparons ce code avec ceux de quelques langages de programmation impératif: + + + # 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; +} + + +en: The structure is the same, but there are some syntax differences. +en: The main part of this tutorial will be dedicated to explaining why. +fr: La structure est la même, mais il y a quelques différences de syntaxe. +fr: La partie principale de ce tutoriel sera consacrée à expliquer cela. + +en: In Haskell there is a `main` function and every object has a type. +en: The type of `main` is `IO ()`. +en: This means `main` will cause side effects. +fr: En Haskell il y a une fonction `main` tous les objets ont un type. +fr: Le type de `main` est `IO ()`. +fr: Cela veut dire que `main` causera des effets secondaires. + +en: Just remember that Haskell can look a lot like mainstream imperative languages. +fr: Rappelez-vous just que Haskell peut ressembler énormément aux principaux langages impératifs. diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/20_very_basic.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/20_very_basic.lhs new file mode 100644 index 0000000..297e21b --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/20_very_basic.lhs @@ -0,0 +1,159 @@ +en:

Very basic Haskell

+fr:

Les bases de Haskell

+ +blogimage("picasso_owl.jpg","Picasso minimal owl") + +en: Before continuing you need to be warned about some essential properties of Haskell. +fr: Avant de continuer, vous devez êtres avertis à propos de propriétés essentielles de Haskell. + +en: _Functional_ +fr: _Fonctionnel_ + +en: Haskell is a functional language. +en: If you have an imperative language background, you'll have to learn a lot of new things. +en: Hopefully many of these new concepts will help you to program even in imperative languages. +fr: Haskell est un langage fonctionnel +fr: Si vous avez déjà travaillé avec un langage impératif, vous devrez apprendre beaucoup de nouvelles choses. +fr: Heureusement beaucoup de ces nouveaux concepts vous aidera à programmer même dans un langage impératif. + +en: _Smart Static Typing_ +fr: _Typage Statique Intelligent_ + +en: Instead of being in your way like in `C`, `C++` or `Java`, the type system is here to help you. +fr: Au lieu de bloquer votre chemin comme en `C`, `C++` ou `Java`, le système de typage est ici pour vous aider. + +en: _Purity_ +fr: _Pureté_ + +en: Generally your functions won't modify anything in the outside world. +en: This means they can't modify the value of a variable, can't get user input, can't write on the screen, can't launch a missile. +en: On the other hand, parallelism will be very easy to achieve. +en: Haskell makes it clear where effects occur and where your code is pure. +en: Also, it will be far easier to reason about your program. +en: Most bugs will be prevented in the pure parts of your program. +fr: Généralement vos fonctions ne modifieront rien du le monde extérieur. +fr: Cela veut dire qu'elles ne peuvent pas modifier la valeur d'une variable, +fr: lire du texte entré par un utilisateur, +fr: écrire sur l'écran, lancer un missile. +fr: D'un autre coté, avoir un code parallèle devient très facile. +fr: Haskell rend très clair où les effets apparaissent et où le code est pur. +fr: De plus, il devient beaucoup plus aisé de raisonner sur son programme. +fr: La majorité des bugs seront évités dans les parties pures de votre programme. + +en: Furthermore, pure functions follow a fundamental law in Haskell: +fr: En outre, les fonctions pures suivent une loi fondamentale en Haskell: + +en: > Applying a function with the same parameters always returns the same value. +fr: > Appliquer une fonction avec les mêmes paramètres retourne toujours la même valeur. + +en: _Laziness_ +fr: _Paresse_ + +en: Laziness by default is a very uncommon language design. +en: By default, Haskell evaluates something only when it is needed. +en: In consequence, it provides a very elegant way to manipulate infinite structures, for example. +fr: La paresse par défaut est un choix de conception de langage très rare. +fr: Par défaut, Haskell évalue quelque chose seulement lorsque cela est nécessaire. +fr: En conséquence, cela fournit un moyen très élégant de manipuler des structures infinies, par exemple. + +en: A last warning about how you should read Haskell code. +en: For me, it is like reading scientific papers. +en: Some parts are very clear, but when you see a formula, just focus and read slower. +en: Also, while learning Haskell, it _really_ doesn't matter much if you don't understand syntax details. +en: If you meet a `>>=`, `<$>`, `<-` or any other weird symbol, just ignore them and follows the flow of the code. +fr: Un dernier avertissement sur comment vous devriez lire le code Haskell. +fr: Pour moi, c'est comme lire des papiers scientifiques. +fr: Quelques parties sont très claires, mais quand vous voyez une formule, concentrez-vous dessus et lisez plus lentement. +fr: De plus, lorsque vous apprenez Haskell, cela n'importe _vraiment_ pas si vous ne comprenez pas les détails syntaxiques. +fr: Si vous voyez un `>>=`, `<$>`, `<-` ou n'importe quel symbole bizarre, ignorez-les et suivez le déroulement du code. + +en:

Function declaration

+fr:

Déclaration de fonctions

+ +en: You might be used to declaring functions like this: +fr: Vous avez déjà dû déclarer des fonctions comme cela: + +en: In `C`: +fr: En `C`: + + +int f(int x, int y) { + return x*x + y*y; +} + + +en: In JavaScript: +fr: En JavaScript: + + +function f(x,y) { + return x*x + y*y; +} + + +en: in Python: +fr: En Python: + + +def f(x,y): + return x*x + y*y + + +en: in Ruby: +fr: En Ruby: + + +def f(x,y) + x*x + y*y +end + + +en: In Scheme: +fr: En Scheme: + + +(define (f x y) + (+ (* x x) (* y y))) + + +en: Finally, the Haskell way is: +fr: Finalement, la manière de faire de Haskell est: + + +f x y = x*x + y*y + + +en: Very clean. No parenthesis, no `def`. +fr: Très propre. Aucune parenthèse, aucun `def`. + +en: Don't forget, Haskell uses functions and types a lot. +en: It is thus very easy to define them. +en: The syntax was particularly well thought out for these objects. +fr: N'oubliez pas, Haskell utilise beaucoup les fonctions et les types. +fr: C'est très facile de les définir. +fr: La syntaxe a été particulièrement réfléchie pour ces objets. + +en:

A Type Example

+fr:

Un exemple de type

+ +en: Although it is not mandatory, type information for functions is usually made +en: explicit. It's not mandatory because the compiler is smart enough to discover +en: it for you. It's a good idea because it indicates intent and understanding. +fr: Même si ce n'est pas obligatoire, les informations de type pour les fonctions sont habituellement déclarées +fr: explicitement. Ce n'est pas indispensable car le compilateur est suffisamment intelligent pour le déduire +fr: à votre place. Cependant, c'est une bonne idée car cela montre bien l'intention du développeur et facilite la compréhension. + +en: Let's play a little. +en: We declare the type using `::` +fr: Jouons un peu. +fr: On déclare le type en utilisant `::` + +> 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/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/21_very_basic.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/21_very_basic.lhs new file mode 100644 index 0000000..8d49e0c --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/21_very_basic.lhs @@ -0,0 +1,23 @@ +en: Now try +fr: Maintenant essayez + +> f :: Int -> Int -> Int +> f x y = x*x + y*y +> +> main = print (f 2.3 4.2) + +en: You should get this error: +fr: Vous devriez avoir cette erreur: + +~~~ +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) +~~~ + +en: The problem: `4.2` isn't an Int. +fr: Le problème est que `4.2` n'est pas de type `Int` (_NDT: Il n'est pas un entier_) diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/22_very_basic.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/22_very_basic.lhs new file mode 100644 index 0000000..1ef0e2a --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/22_very_basic.lhs @@ -0,0 +1,170 @@ +en: The solution: don't declare a type for `f` for the moment and let Haskell infer the most general type for us: +fr: La soulution: ne déclarez pas de type pour `f` pour le moment et laissez Haskell inférer le type le plus général pour nous: + +> f x y = x*x + y*y +> +> main = print (f 2.3 4.2) + +en: It works! +en: Luckily, we don't have to declare a new function for every single type. +en: For example, in `C`, you'll have to declare a function for `int`, for `float`, for `long`, for `double`, etc... +fr: Maintenant, ça marche! +fr: Heureursement, nous n'avons pas à déclarer un nouvelle fonction pour chaque type différent. +fr: Par exemple, en `C`, vous auriez dû déclarer un fonction pour `int`, pour `float`, pour `long`, pour `double`, etc... + +en: But, what type should we declare? +en: To discover the type Haskell has found for us, just launch ghci: +fr: Mais quel type devons nous déclarer? +fr: Pour découvrir le type que Haskell a trouvé pour nous, lançons 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
+
+ +en: Uh? What is this strange type? +fr: Hein? Quel ce type étrange? + +~~~ +Num a => a -> a -> a +~~~ + +en: First, let's focus on the right part `a -> a -> a`. +en: To understand it, just look at a list of progressive examples: +fr: Premièrement, concentrons-nous sur la partie de droite: `a -> a -> a`. +fr: Pour le comprendre, regardez cette liste d'exemples progressifs: + +en: -------------------------------------------------------------------------------------------------- +en: The written type Its meaning +en: -------------------------- ----------------------------------------------------------------------- +en: `Int` the type `Int` +en: +en: `Int -> Int` the type function from `Int` to `Int` +en: +en: `Float -> Int` the type function from `Float` to `Int` +en: +en: `a -> Int` the type function from any type to `Int` +en: +en: `a -> a` the type function from any type `a` to the same type `a` +en: +en: `a -> a -> a` the type function of two arguments of any type `a` to the same type `a` +en: -------------------------------------------------------------------------------------------------- +fr: -------------------------------------------------------------------------------------------------------------------------------------- +fr: Le type écrit Son sens +fr: -------------------------- ----------------------------------------------------------------------------------------------------------- +fr: `Int` Le type `Int` +fr: +fr: `Int -> Int` Le type de la fonction qui prend un `Int` et retourne un `Int` +fr: +fr: `Float -> Int` Le type de la fonction qui prend un `Float` et retourne un `Int` +fr: +fr: `a -> Int` Le type de la fonction qui prend n'importe quel type de variable et retourne un `Int` +fr: +fr: `a -> a` Le type de la fonction qui prend n'importe quel type `a` et retourne une variable du même type `a` +fr: +fr: `a -> a -> a` Le type de la fonction qui prend de arguments de n'importe quel type`a` et retourne une variable de type `a` +fr: -------------------------------------------------------------------------------------------------------------------------------------- + +en: In the type `a -> a -> a`, the letter `a` is a _type variable_. +en: It means `f` is a function with two arguments and both arguments and the result have the same type. +en: The type variable `a` could take many different type values. +en: For example `Int`, `Integer`, `Float`... +fr: Dans le type `a -> a -> a`, la lettre `a` est une _variable de type_. +fr: Cela signifie que `f` est une fonction avec deux arguments et que les deux arguments et le résultat ont le même type. +fr: La variable de type `a` peut prendre de nombreuses valeurs différentes +fr: Par exemple `Int`, `Integer`, `Float`... + +en: So instead of having a forced type like in `C` and having to declare a function +en: for `int`, `long`, `float`, `double`, etc., we declare only one function like +en: in a dynamically typed language. +fr: Donc à la place d'avoir un type forcé comme en `C` et de devoir déclarer une fonction +fr: pour `int`, `long`, `float`, `double`, etc., nous déclarons une seule fonction comme +fr: dans un langage typé de façon dynamique. + +en: This is sometimes called parametric polymorphism. It's also called having your +en: cake and eating it too. +fr: C'est parfois appelé le polymorphisme paramétrique. C'est aussi appelé avoir un +fr: gâteau et le manger. + +en: Generally `a` can be any type, for example a `String` or an `Int`, but also +en: more complex types, like `Trees`, other functions, etc. But here our type is +en: prefixed with `Num a => `. +fr: Généralement `a` peut être de n'importe quel type, par exemple un `String` ou un `Int`, mais aussi +fr: des types plus complexes comme `Trees`, d'autres fonctions, etc. Mais ici notre type est +fr: préfixé par `Num a => `. + +en: `Num` is a _type class_. +en: A type class can be understood as a set of types. +en: `Num` contains only types which behave like numbers. +en: More precisely, `Num` is class containing types which implement a specific list of functions, and in particular `(+)` and `(*)`. +fr: `Num` est une _classe de type_. +fr: Une classe de type peut être comprise comme un ensemble de types +fr: `Num` contient seulement les types qui se comportent comme des nombres. +fr: Plus précisement, `Num` est une classe qui contient des types qui implémentent une liste spécifique de fonctions, +fr: en particulier `(+)` et `(*)`. + +en: Type classes are a very powerful language construct. +en: We can do some incredibly powerful stuff with this. +en: More on this later. +fr: Les classes de types sont une structure de langage très puissante. +fr: Nous pouvons faire des trucs incroyablement puissants avec. +fr: Nous verrons cela plus tard. + +en: Finally, `Num a => a -> a -> a` means: +fr: Finalement, `Num a => a -> a -> a` signifie: + +en: Let `a` be a type belonging to the `Num` type class. +en: This is a function from type `a` to (`a -> a`). +fr: soit `a` un type qui appartient à la classe `Num`. +fr: C'est une fonction qui prend une variable de type `a` et retourne une fonction de type `(a -> a)` + +en: Yes, strange. +en: In fact, in Haskell no function really has two arguments. +en: Instead all functions have only one argument. +en: But we will note that taking two arguments is equivalent to taking one argument and returning a function taking the second argument as a parameter. +fr: Oui, c'est étrange. +fr: En fait, en Haskell aucune fonction ne prend réellement deux arguments. +fr: Au lieu de cela toutes les fonctions n'ont qu'un argument unique. +fr: Mais nous retiendrons que prendre deux arguments est équivalent à n'en prendre qu'un et à retourner une fonction qui prend le second argument en paramètre. + +en: More precisely `f 3 4` is equivalent to `(f 3) 4`. +en: Note `f 3` is a function: +fr: Plus précisement `f 3 4` est équivalent à `(f 3) 4 `. +fr: Remarque: `f 3` est une fonction: + +~~~ +f :: Num a => a -> a -> a + +g :: Num a => a -> a +g = f 3 + +g y ⇔ 3*3 + y*y +~~~ + +en: Another notation exists for functions. +en: The lambda notation allows us to create functions without assigning them a name. +en: We call them anonymous functions. +en: We could also have written: +fr: Une autre notation existe pour les fonctions. +fr: La notation lambda nous autorise à créer des fonctions sans leur assigner un nom. +fr: On les appelle des fonctions anonymes. +fr: nous aurions donc pu écrire: + +~~~ +g = \y -> 3*3 + y*y +~~~ + +en: The `\` is used because it looks like `λ` and is ASCII. +fr: Le `\` esst utilisé car il ressemble à un `λ` et est un caractère ASCII. + +en: If you are not used to functional programming your brain should be starting to heat up. +en: It is time to make a real application. +fr: Si vous n'êtes pas habitué à la programmation fonctionnelle, votre cerveau devrait commencer à chauffer +fr: Il est temps de faire une vraie application. diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/23_very_basic.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/23_very_basic.lhs new file mode 100644 index 0000000..f6881e3 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/23_very_basic.lhs @@ -0,0 +1,12 @@ +en: But just before that, we should verify the type system works as expected: +fr: Mais juste avant cela, nous devrions vérifier que le système de type marche comme nous le supposons: + +> f :: Num a => a -> a -> a +> f x y = x*x + y*y +> +> main = print (f 3 2.4) + +en: It works, because, `3` is a valid representation both for Fractional numbers like Float and for Integer. +en: As `2.4` is a Fractional number, `3` is then interpreted as being also a Fractional number. +fr: Cela fonctionne, car `3` est une représentation valide autant pour les nombres fractionnaires comme Float que pour les entiers. +fr: Comme `2.4` est un nombre fractionnaire, `3` est interprété comme une autre nombre fractionnaire diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/24_very_basic.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/24_very_basic.lhs new file mode 100644 index 0000000..8512c6b --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/10_Introduction/24_very_basic.lhs @@ -0,0 +1,24 @@ +en: If we force our function to work with different types, it will fail: +fr: Si nous forçons notre fonction à travailler avec des types différents, le test échouera: + +> f :: Num a => a -> a -> a +> f x y = x*x + y*y +> +> x :: Int +> x = 3 +> y :: Float +> y = 2.4 +> -- won't work because type x ≠ type y +> main = print (f x y) + +en: The compiler complains. +en: The two parameters must have the same type. +fr: Le compilateur se plaint. +fr: Les deux paramètres doivent avoir le même type. + +en: If you believe that this is a bad idea, and that the compiler should make the transformation +en: from one type to another for you, you should really watch this great (and funny) video: +en: [WAT](https://www.destroyallsoftware.com/talks/wat) +fr: Si vous pensez que c'est une mauvaise idée et que le compilateur devrait faire la transformation +fr: depuis un type à un autre pour vous, vous devriez vraiment regarder cette vidéo géniale (et amusante): +fr: [WAT](https://www.destroyallsoftware.com/talks/wat) (_NDT: En Anglais_) diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/20_Essential_Haskell/00_notations.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/20_Essential_Haskell/00_notations.lhs new file mode 100644 index 0000000..7744b8d --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/20_Essential_Haskell/00_notations.lhs @@ -0,0 +1,171 @@ +en:

Essential Haskell

+fr:

Notions essentielles

+ +blogimage("kandinsky_gugg.jpg","Kandinsky Gugg") + +en: I suggest that you skim this part. +en: Think of it as a reference. +en: Haskell has a lot of features. +en: A lot of information is missing here. +en: Come back here if the notation feels strange. +fr: Je vous suggère de seulement survoler cette partie +fr: Pensez-y seulement comme à une référence. +fr: Haskell a beaucoup de caractèristiques +fr: Il manque beaucoup d'informations ici. +fr: Revenz ici si la notation vous semble étrange. + +en: I use the `⇔` symbol to state that two expression are equivalent. +en: It is a meta notation, `⇔` does not exists in Haskell. +en: I will also use `⇒` to show what the return value of an expression is. +fr: J'utilise le symbole `⇔` pour signifier que deux expressions sont équivalentes. +fr: C'est une notation extérieure, `⇔` n'existe pas en Haskell. +fr: Je vais aussi utiliser le symoble `⇒` quelle est la valeur que retourne une fonction. + +

Notations

+ +en:
Arithmetic
+fr:
Arithmétique
+ +~~~ +3 + 2 * 6 / 3 ⇔ 3 + ((2*6)/3) +~~~ + +en:
Logic
+fr:
Logique
+ +~~~ +True || False ⇒ True +True && False ⇒ False +True == False ⇒ False +en: True /= False ⇒ True (/=) is the operator for different +fr: True /= False ⇒ True (/=) est l'opérateur pour "différent de" +~~~ + +en:
Powers
+fr:
Puissances
+ +~~~ +en: x^n for n an integral (understand Int or Integer) +en: x**y for y any kind of number (Float for example) +fr: x^n pour n un entier (comprenez Int ou Integer) +fr: x**y pour y tout type de nombre (Float par exemple) +~~~ + +en: `Integer` has no limit except the capacity of your machine: +fr: `Integer` n'a aucune limite à part la capacité de votre machine: + +~~~ +4^103 +102844034832575377634685573909834406561420991602098741459288064 +~~~ + +Yeah! +en: And also rational numbers FTW! +en: But you need to import the module `Data.Ratio`: +fr: Et aussi les nombres rationnels! +fr: Mais vous avez besoin d'importer le module `Data.Ratio` + +~~~ +$ ghci +.... +Prelude> :m Data.Ratio +Data.Ratio> (11 % 15) * (5 % 3) +11 % 9 +~~~ + +en:
Lists
+fr:
Listes
+ +~~~ +en: [] ⇔ empty list +en: [1,2,3] ⇔ List of integral +en: ["foo","bar","baz"] ⇔ List of String +en: 1:[2,3] ⇔ [1,2,3], (:) prepend one element +en: 1:2:[] ⇔ [1,2] +en: [1,2] ++ [3,4] ⇔ [1,2,3,4], (++) concatenate +en: [1,2,3] ++ ["foo"] ⇔ ERROR String ≠ Integral +en: [1..4] ⇔ [1,2,3,4] +en: [1,3..10] ⇔ [1,3,5,7,9] +en: [2,3,5,7,11..100] ⇔ ERROR! I am not so smart! +en: [10,9..1] ⇔ [10,9,8,7,6,5,4,3,2,1] +fr: [] ⇔ liste vide +fr: [1,2,3] ⇔ Liste d'entiers +fr: ["foo","bar","baz"] ⇔ Liste de chaînes de caractères +fr: 1:[2,3] ⇔ [1,2,3], (:) ajoute un élément au début +fr: 1:2:[] ⇔ [1,2] +fr: [1,2] ++ [3,4] ⇔ [1,2,3,4], (++) concaténation de deux listes +fr: [1,2,3] ++ ["foo"] ⇔ ERREUR String ≠ Integral +fr: [1..4] ⇔ [1,2,3,4] +fr: [1,3..10] ⇔ [1,3,5,7,9] +fr: [2,3,5,7,11..100] ⇔ ERREUR! Je ne suis pas si intelligent! +fr: [10,9..1] ⇔ [10,9,8,7,6,5,4,3,2,1] +~~~ + +en:
Strings
+fr:
Chaînes de caractères
+ +en: In Haskell strings are list of `Char`. +fr: En Haskell les chaînes de caractères sont des listes de `Char`. + +~~~ +'a' :: Char +"a" :: [Char] +"" ⇔ [] +"ab" ⇔ ['a','b'] ⇔ 'a':"b" ⇔ 'a':['b'] ⇔ 'a':'b':[] +"abc" ⇔ "ab"++"c" +~~~ + +en: > _Remark_: +en: > In real code you shouldn't use list of char to represent text. +en: > You should mostly use `Data.Text` instead. +en: > If you want to represent a stream of ASCII char, you should use `Data.ByteString`. +fr: > _Remarque_: +fr: > Dans un vrai code vous n'utiliserez pas des listes de char pour représenter du texte. +fr: > Vous utiliserez plus souvent `Data.Text` à la place. +fr: > Si vous voulez représenter un chapelet de caractères ASCII, vous utiliserez `Data.ByteString`. + +
Tuples
+ +en: The type of couple is `(a,b)`. +en: Elements in a tuple can have different types. +fr: Le type d'un couple est `(a,b)`. +fr: Les éléments d'un tuple peuvent avoir des types différents. + +~~~ +en: -- All these tuples are valid +fr: -- tous ces tuples sont valides +(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 +~~~ + +en:
Deal with parentheses
+fr:
Traiter avec les parenthèses
+ +en: To remove some parentheses you can use two functions: `($)` and `(.)`. +fr: Pour enlever des parenthèses vous pouvez utiliser deux fonctions: `($)` et `(.)`. + +~~~ +en: -- By default: +fr: -- Par défaut: +f g h x ⇔ (((f g) h) x) + +en: -- the $ replace parenthesis from the $ +en: -- to the end of the expression +fr: -- le $ remplace les parenthèses depuis le $ +fr: -- jusqu'à la fin de l'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)) + +en: -- (.) the composition function +fr: -- (.) permet de faire des compositions de fonctions +(f . g) x ⇔ f (g x) +(f . g . h) x ⇔ f (g (h x)) +~~~ diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/20_Essential_Haskell/10a_Functions.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/20_Essential_Haskell/10a_Functions.lhs new file mode 100644 index 0000000..5d543b2 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/01_basic/20_Essential_Haskell/10a_Functions.lhs @@ -0,0 +1,99 @@ +en:

Useful notations for functions

+fr:

Notations utiles pour les fonctions

+ +en: Just a reminder: +fr: Juste un mémo: + +~~~ +en: x :: Int ⇔ x is of type Int +en: x :: a ⇔ x can be of any type +en: x :: Num a => a ⇔ x can be any type a +en: such that a belongs to Num type class +en: f :: a -> b ⇔ f is a function from a to b +en: f :: a -> b -> c ⇔ f is a function from a to (b→c) +en: f :: (a -> b) -> c ⇔ f is a function from (a→b) to c +fr: x :: Int ⇔ x est de type Int +fr: x :: a ⇔ x peut être de n'importe quel type +fr: x :: Num a => a ⇔ x peut être de n'importe quel type a +fr: tant qu' a appartient à la classe de type Num +fr: f :: a -> b ⇔ f est une fonction qui prend un a et retourne un b +fr: f :: a -> b -> c ⇔ f est une fonction qui prend un a et retourne un (b→c) +fr: f :: (a -> b) -> c ⇔ f est une fonction qui prend un (a→b) et retourne un c +~~~ + +en: Remember that defining the type of a function before its declaration isn't mandatory. +en: Haskell infers the most general type for you. +en: But it is considered a good practice to do so. +fr: Rappelez-vous que définir le type d'une fonction avant sa déclaration n'est pas obligatoire. +fr: Haskell infère le type le plus général pour vous. +fr: Mais c'est considéré comme une bonne pratique. + +en: _Infix notation_ +fr: _Notation Infixée_ + +> square :: Num a => a -> a +> square x = x^2 + +en: Note `^` uses infix notation. +en: For each infix operator there its associated prefix notation. +en: You just have to put it inside parenthesis. +fr: Remarquez que `^` utilise une notation infixée. +fr: Pour chaque opérateur infixe il y a une notation préfixée associée. +fr: Vous devz juste l'écrire entre parenthèses. + +> square' x = (^) x 2 +> +> square'' x = (^2) x + +en: We can remove `x` in the left and right side! +en: It's called η-reduction. +fr: Nous pouvons enlever le `x` dans les parties de gauche et de droite! +fr: On appelle cela la η-réduction + +> square''' = (^2) + +en: Note we can declare functions with `'` in their name. +en: Here: +fr: Rmarquez qu nous pouvons déclarer des fonctions avec `'` dans leur nom. +fr: Exemples: + + > `square` ⇔ `square'` ⇔ `square''` ⇔ `square'''` + +_Tests_ + +en: An implementation of the absolute function. +fr: Une implémentation de la fonction absolue. + +> absolute :: (Ord a, Num a) => a -> a +> absolute x = if x >= 0 then x else -x + +en: Note: the `if .. then .. else` Haskell notation is more like the +en: `¤?¤:¤` C operator. You cannot forget the `else`. +fr: Remarque: la notation de Haskell pour le `if .. then .. else` ressemble plus +fr: à l'opérateur `¤?¤:¤` en C. Le `else` est obligatoire. + +en: Another equivalent version: +fr: Une version équivalente: + +> absolute' x +> | x >= 0 = x +> | otherwise = -x + +en: > Notation warning: indentation is _important_ in Haskell. +en: > Like in Python, bad indentation can break your code! +fr: > Avertissement: l'indentation est _importante_ en Haskell. +fr: > Comme en Python, une mauvaise indentation peut détruire votre 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/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/10_Functions.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/10_Functions.lhs new file mode 100644 index 0000000..02df0e4 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/10_Functions.lhs @@ -0,0 +1,133 @@ +en:

Hard Part

+fr:

La Partie Difficile

+ +en: The hard part can now begin. +fr: La partie difficile peut maintenant commencer. + +en:

Functional style

+fr:

Le style fonctionnel

+ +blogimage("hr_giger_biomechanicallandscape_500.jpg","Biomechanical Landscape by H.R. Giger") + +en: In this section, I will give a short example of the impressive refactoring ability provided by Haskell. +en: We will select a problem and solve it in a standard imperative way. +en: Then I will make the code evolve. +en: The end result will be both more elegant and easier to adapt. +fr: Dans cette section, je vais vous donner un court exemple de l'impressionante capacité de remaniement de Haskell. +fr: Nous allons sélectionner un problème et le résoudre à la manière d'un langage impératif standard. +fr: Ensuite, je ferais évoluer le code. +fr: Le résultat final sera plus élégant et plus facile à adapter. + +en: Let's solve the following problem: +fr: résolvons les problèmes suivants: + +en: > Given a list of integers, return the sum of the even numbers in the list. +fr: > Soit une liste d'entiers, retourner la somme des nombres pairs de cette liste. + > +en: > example: +fr: > exemple: + > `[1,2,3,4,5] ⇒ 2 + 4 ⇒ 6` + +en: To show differences between functional and imperative approaches, +en: I'll start by providing an imperative solution (in JavaScript): +fr: Pour montrer les différences entre les approches fonctionnelle et impérative, +fr: je vais commencer par donner la solution impérative (en 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; +} + + +en: In Haskell, by contrast, we don't have variables or a for loop. +en: One solution to achieve the same result without loops is to use recursion. +fr: En Haskell, en revanche, nous n'avons pas de variables ou un boucle `for`. +fr: Une des solutions pour parvenir au même résultat sans boucles est d'utiliser la récursion. + +en: > _Remark_: +en: > Recursion is generally perceived as slow in imperative languages. +en: > But this is generally not the case in functional programming. +en: > Most of the time Haskell will handle recursive functions efficiently. +fr: > _Remarque_: +fr: > La récursion est souvent perçue comme lente dans les langages impératifs. +fr: > Mais ce n'est généralement pas le cas en programmation fonctionnelle. +fr: > La plupart du temps Haskell gérera les fonctions récursives efficacement. + +en: Here is a `C` version of the recursive function. +en: Note that for simplicity I assume the int list ends with the first `0` value. +fr: Voici la version `C` de la fonction récursive. +fr: Remarquez que je suppose que la liste d'int fini avec la première valeur `0`. + + + +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); + } + } +} + + +en: Keep this code in mind. We will translate it into Haskell. +en: First, however, I need to introduce three simple but useful functions we will use: +fr: Gardez ce code à l'esprit. Nous allons le traduire en Haskell. +fr: Premièrement, + + +even :: Integral a => a -> Bool +head :: [a] -> a +tail :: [a] -> [a] + + +en: `even` verifies if a number is even. +fr: `even` vérifie si un nombre est pair. + + +even :: Integral a => a -> Bool +even 3 ⇒ False +even 2 ⇒ True + + +en: `head` returns the first element of a list: +fr: `head` retourne le premier élément d'une liste: + + +head :: [a] -> a +head [1,2,3] ⇒ 1 +head [] ⇒ ERROR + + +en: `tail` returns all elements of a list, except the first: +fr: `tail` retourne tous les éléments d'une liste, sauf le premier: + + +tail :: [a] -> [a] +tail [1,2,3] ⇒ [2,3] +tail [3] ⇒ [] +en: tail [] ⇒ ERROR +fr: tail [] ⇒ ERREUR + + +en: Note that for any non empty list `l`, +en: `l ⇔ (head l):(tail l)` +fr: Remarquez que pour toute liste non-vide `l`, +fr: `l ⇔ (head l):(tail l)` diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/11_Functions.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/11_Functions.lhs new file mode 100644 index 0000000..cc34f13 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/11_Functions.lhs @@ -0,0 +1,80 @@ +en: The first Haskell solution. +en: The function `evenSum` returns the sum of all even numbers in a list: +fr: La première solution en Haskell. +fr: La fonction `evenSum` retourne la somme de tous les nombres pairs d'une liste: + +> -- 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 + +en: To test a function you can use `ghci`: +fr: Pour tester une fonction nous pouvons utiliser `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
+
+ +en: Here is an example of execution[^2]: +fr: Voici un exemple d'exécution[^2]: + +en: [^2]: I know I'm cheating. But I will talk about non-strictness later. +fr: [^2]: Je sais que je triche. Mais je parlerais de la non-rigueur plus tard. + +
+*Main> evenSum [1..5]
+accumSum 0 [1,2,3,4,5]
+en: 1 is odd
+fr: 1 est impair
+accumSum 0 [2,3,4,5]
+en: 2 is even
+fr: 2 est pair
+accumSum (0+2) [3,4,5]
+en: 3 is odd
+fr: 3 est impair
+accumSum (0+2) [4,5]
+en: 2 is even
+fr: 4 est pair
+accumSum (0+2+4) [5]
+en: 5 is odd
+fr: 5 est impair
+accumSum (0+2+4) []
+l == []
+0+2+4
+0+6
+6
+
+ +en: Coming from an imperative language all should seem right. +en: In fact, many things can be improved here. +en: First, we can generalize the type. +fr: En venant d'un langage impératif, tout devrait vous sembler juste. +fr: En fait, beaucoup de choses peuvent être améliorées ici. +fr: Tout d'abord, nous pouvons généraliser le type. + + +evenSum :: Integral a => [a] -> a + + +
+ +> main = do print $ evenSum [1..10] + +
diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/12_Functions.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/12_Functions.lhs new file mode 100644 index 0000000..8907b1e --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/12_Functions.lhs @@ -0,0 +1,23 @@ +en: Next, we can use sub functions using `where` or `let`. +fr: Ensuite, nous pouvons utiliser des sous-fonctions grâce à `where` et `let`. +en: This way our `accumSum` function won't pollute the namespace of our module. +fr: Ansi, notre fonction `accumSum` ne polluera pas le _namespace_ de notre module + +> -- 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/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/13_Functions.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/13_Functions.lhs new file mode 100644 index 0000000..a2849c4 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/13_Functions.lhs @@ -0,0 +1,64 @@ +en: Next, we can use pattern matching. +fr: Puis on utilise le _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 + +en: What is pattern matching? +en: Use values instead of general parameter names[^021301]. +fr: Qu'est ce que le _pattern matching_ ? +fr: Il s'agit d'utiliser des valeurs au lieu de noms de paramètres généraux. + +en: [^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). +fr: [^021301]: Pour les plus courageux, une explication plus complète du _pattern matching_ peut être trouvée [ici](http://www.cs.auckland.ac.nz/references/haskell/haskell-intro-html/patterns.html) (_NdT: En anglais_) + +en: Instead of saying: `foo l = if l == [] then else ` +en: You simply state: +fr: Au lieu d'écrire: `foo l = if l == [] then else ` +fr: Vous écrivez tout simplement : + + +foo [] = +foo l = + + +en: But pattern matching goes even further. +en: It is also able to inspect the inner data of a complex value. +en: We can replace +fr: Mais le _pattern matching_ peut aller encore plus loin. +fr: Il est également capable d'inspect les données internes d'un valeur complexe. +fr: Nous pouvons ainsi remplacer + + +foo l = let x = head l + xs = tail l + in if even x + then foo (n+x) xs + else foo n xs + + +en: with +fr: par + + +foo (x:xs) = if even x + then foo (n+x) xs + else foo n xs + + +en: This is a very useful feature. +en: It makes our code both terser and easier to read. +fr: C'est une caractéristique très utile. +fr: Notre code est ainsi plus concis et plus facile à lire. + +
+ +> main = print $ evenSum [1..10] + +
diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/14_Functions.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/14_Functions.lhs new file mode 100644 index 0000000..9a92064 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/14_Functions.lhs @@ -0,0 +1,37 @@ +en: In Haskell you can simplify function definitions by η-reducing them. +en: For example, instead of writing: +fr: Avec Haskell, nous pouvons simplifier les défitions des fonctions en les _η-réduisant_ . +fr: Par exemple, au lieu d'écrire: + + +en: f x = (some expresion) x +fr: f x = (expression) x + + +en: you can simply write +fr: Nous pouvons écrire + + +en: f = some expression +fr: f = expression + + +en: We use this method to remove the `l`: +fr: Utilisons cette méthode pour retirer le `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/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/15_Functions.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/15_Functions.lhs new file mode 100644 index 0000000..6007a34 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/15_Functions.lhs @@ -0,0 +1,123 @@ +en:

Higher Order Functions

+fr:

Fonctions d'ordre supérieur

+ +blogimage("escher_polygon.png","Escher") + +en: To make things even better we should use higher order functions. +en: What are these beasts? +en: Higher order functions are functions taking functions as parameters. +fr: Pour rendre les choses plus faciles, nous devrions utiliser des fonctions d'ordre supérieur. +fr: Ce sont des fonctions qui prennent des fonctions en paramètres + +en: Here are some examples: +fr: Voici quelques exemples: + + +filter :: (a -> Bool) -> [a] -> [a] +map :: (a -> b) -> [a] -> [b] +foldl :: (a -> b -> a) -> a -> [b] -> a + + +en: Let's proceed by small steps. +fr: Procédons par étapes. + + +-- Version 5 +evenSum l = mysum 0 (filter even l) + where + mysum n [] = n + mysum n (x:xs) = mysum (n+x) xs + + +en: where +fr: où + + +filter even [1..10] ⇔ [2,4,6,8,10] + + +en: The function `filter` takes a function of type (`a -> Bool`) and a list of type `[a]`. +en: It returns a list containing only elements for which the function returned `true`. +fr: La fonction `filter` prend une fonction du type (`a -> Bool`) et une liste de type `[a]`. +fr: Elle retourne une liste qui contient seulement les élements pour qui la fonction a retourné `True`. + +en: Our next step is to use another technique to accomplish the same thing as a loop. +en: We will use the `foldl` function to accumulate a value as we pass through the list. +en: The function `foldl` captures a general coding pattern: +fr: La prochaine étape est d'utiliser une autre technique pour accomplir la même chose qu'une boucle. +fr: Nous allons utiliser la fonction `foldl` pour accumuler une valeur au fur et à mesure que l'on parcoure la liste. +fr: La fonction `foldl` capture un modèle de code général: + +
+    myfunc list = foo initialValue list
+    foo accumulated []     = accumulated
+    foo tmpValue    (x:xs) = foo (bar tmpValue x) xs
+
+ +en: Which can be replaced by: +fr: Qui peut être remplacé par: + +
+myfunc list = foldl bar initialValue list
+
+ +en: If you really want to know how the magic works, here is the definition of `foldl`: +fr: Si vous souhaitez vraiment savoir comment la magie se produit, voici la définition de `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 + + +en: But as Haskell is lazy, it doesn't evaluate `(f z x)` and simply pushes it onto the stack. +en: This is why we generally use `foldl'` instead of `foldl`; +en: `foldl'` is a _strict_ version of `foldl`. +en: If you don't understand what lazy and strict means, +en: don't worry, just follow the code as if `foldl` and `foldl'` were identical. +fr: Mais comme Haskell est paresseux, il n'évalue pas `(f z x)` et le met simplement dans la pile. +fr: C'est pourquoi on utilise généralement `foldl'`, une version _stricte_ de `foldl`, +fr: Si vous ne comprenez pas encore ce que _paresseux_ ou _strict_ signifie, +fr: ne vous inquiétez pas, suivez le code comme si `foldl'` et `foldl` étaient identiques + +en: Now our new version of `evenSum` becomes: +fr: Maintenant notre version de `evenSum` devient: + + +-- Version 6 +en: -- foldl' isn't accessible by default +en: -- we need to import it from the module Data.List +fr: -- foldl' n'est pas accessible par défaut +fr: -- nous devons l'importer depuis le module Data.List +import Data.List +evenSum l = foldl' mysum 0 (filter even l) + where mysum acc value = acc + value + + +en: We can also simplify this by using directly a lambda notation. +en: This way we don't have to create the temporary name `mysum`. +fr: Nous pouvons aussi simplifier cela en utilisant une _lambda-notation_. +fr: Ainsi nous n'avons pas besoin de créer le nom temporaire `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) + +en: And of course, we note that +fr: Et bien sûr, nous remarquons que + + +(\x y -> x+y) ⇔ (+) + + +
+ +> main = print $ evenSum [1..10] + +
diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/16_Functions.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/16_Functions.lhs new file mode 100644 index 0000000..fc1c1c1 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/16_Functions.lhs @@ -0,0 +1,148 @@ +en: Finally +fr: Finalement + + +-- Version 8 +import Data.List (foldl') +evenSum :: Integral a => [a] -> a +evenSum l = foldl' (+) 0 (filter even l) + + +en: `foldl'` isn't the easiest function to grasp. +en: If you are not used to it, you should study it a bit. +fr: `foldl'` n'est pas la fonction la plus facile à prendre en main. +fr: Si vous n'y êtes pas habitué, vous devriez l'étudier un peu. + +en: To help you understand what's going on here, let's look at a step by step evaluation: +fr: Pour mieux comprendre ce qui se passe ici, étudions une évaluation étape par étape: + +
+  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
+
+ + +en: Another useful higher order function is `(.)`. +en: The `(.)` function corresponds to mathematical composition. +fr: Une autre fonction d'ordre supérieur utile est `(.)`. +fr: Elle correspond à une composition en mathématiques. + + +(f . g . h) x ⇔ f ( g (h x)) + + +en: We can take advantage of this operator to η-reduce our function: +fr: Nous pouvons profiter de cet opérateur pour η-réduire notre fonction: + + +-- Version 9 +import Data.List (foldl') +evenSum :: Integral a => [a] -> a +evenSum = (foldl' (+) 0) . (filter even) + + +en: Also, we could rename some parts to make it clearer: +fr: Nous pouvons maintenant renommer certaines parties pour rendre le tout plus clair: + +> -- Version 10 +> import Data.List (foldl') +> sum' :: (Num a) => [a] -> a +> sum' = foldl' (+) 0 +> evenSum :: Integral a => [a] -> a +> evenSum = sum' . (filter even) +> + +en: It is time to discuss the direction our code has moved as we introduced more functional idioms. +en: What did we gain by using higher order functions? +fr: Il est temps de discuter de la direction qu'a pris notre code depuis que nous avons introduit plus d'idiomes fonctionnels. +fr: Que gagnons-nous à utiliser des fonctions d'ordre supérieur? + +en: At first, you might think the main difference is terseness. But in fact, it has +en: more to do with better thinking. Suppose we want to modify our function +en: slightly, for example, to get the sum of all even squares of elements of the list. +fr: D'abord, vous pourriez penser que la principale différence est la brièveté. Mais en réalité, +fr: il s'agit d'une meilleure façon de penser. Supposons que nous voulons modifier légèrement notre fonction, +fr: par exemple, pour qu'elle renvoie la somme de tous les carrés pairs des éléments de la liste. + +~~~ +[1,2,3,4] ▷ [1,4,9,16] ▷ [4,16] ▷ 20 +~~~ + +en: Updating version 10 is extremely easy: +fr: Mettre la version 10 à jour est très facile: + +> squareEvenSum = sum' . (filter even) . (map (^2)) +> squareEvenSum' = evenSum . (map (^2)) + +en: We just had to add another "transformation function"[^0216]. +fr: Nous avons juste eu à ajouter une autre "fonction de trabsformation"[^0216]. + +~~~ +map (^2) [1,2,3,4] ⇔ [1,4,9,16] +~~~ + +en: The `map` function simply applies a function to all the elements of a list. +fr: La fonction `map` applique simplementune fonction à tous les élements d'une liste. + +en: We didn't have to modify anything _inside_ the function definition. +en: This makes the code more modular. +en: But in addition you can think more mathematically about your function. +en: You can also use your function interchangably with others, as needed. +en: That is, you can compose, map, fold, filter using your new function. +fr: Nous n'avons rien modifié _à l'intérieur_ de notre définition de fonction. +fr: Cela rend le code plus modulaire. +fr: En plus de cela, vous pouvez penser à votre fonction plus mathématiquement. +fr: Vous pouvez aussi utilier votre fonction avec d'autres, au besoin: +fr: vous pouvez utiliser `compose`, `map`, `fold` ou `filter` sur notre nouvelle fonction. + +en: Modifying version 1 is left as an exercise to the reader ☺. +fr: Modifier la version 1 est laissé comme un exercice pour le lecteur ☺. + +en: If you believe we have reached the end of generalization, then know you are very wrong. +en: For example, there is a way to not only use this function on lists but on any recursive type. +en: 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/01/db-utwente-40501F46.pdf). +fr: Si vous croyez avoir atteint le bout de la généralisation, vous avez tout faux. +fr: Par example, il y a un moyen d'utiliser cette fonction non seulement sur les listes mais aussi sur n'importe quel type récursif. +fr: Si vous voulez savoir comment, je vous suggère de lire cet article: [Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire by Meijer, Fokkinga and Paterson](http://eprints.eemcs.utwente.nl/7281/01/db-utwente-40501F46.pdf) (_NDT: en anglais, mais là vous vous en seriez douté je pense ☺_) + +en: This example should show you how great pure functional programming is. +en: Unfortunately, using pure functional programming isn't well suited to all usages. +en: Or at least such a language hasn't been found yet. +fr: Cet exemple montre à quel point la programmation fonctionnelle pure est géniale. +fr: Malheureusement, utiliser cet outil n'est pas adapté à tous les besoins. +fr: Ou alors un langage qui le premettrait n'a pas encore été trouvé. + +en: One of the great powers of Haskell is the ability to create DSLs +en: (Domain Specific Language) +en: making it easy to change the programming paradigm. +fr: Une des grands pouvoirs de Haskell est sa capacité à créer des DSLs +fr: (_Domain Specific Language_, en français : _langage spécifique à un domaine_) +fr: Il est ainsi facile de changer le pardigme de programmation + +en: In fact, Haskell is also great when you want to write imperative style +en: programming. Understanding this was really hard for me to grasp when first +en: learning Haskell. A lot of effort tends to go into explaining the superiority +en: of the functional approach. Then when you start using an imperative style with +en: Haskell, it can be hard to understand when and how to use it. +fr: En fait, Haskell peut très bien vous permettre d'écrire des programmes impératifs. +fr: Comprendre cela a été très difficile pour moi lorsque j'apprenais Haskell. +fr: Beaucoup d'efforts tendent à expliquer la supériorité de l'approche fonctionnele. +fr: Puis lorsque vous commencez à utliser le style impératif en Haskell, +fr: Il peut être difficile de comprendre quand et où l'utliser. + +en: But before talking about this Haskell super-power, we must talk about another +en: essential aspect of Haskell: _Types_. +fr: Mais avant de parler de ce super-pouvoir de Haskell, nous devons parler +fr: d'un autre aspet essentiel: les _Types_. + +
+ +> main = print $ evenSum [1..10] + +
diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/20_Types.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/20_Types.lhs new file mode 100644 index 0000000..175db29 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/20_Types.lhs @@ -0,0 +1,137 @@ +en:

Types

+fr:

Les 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 mark any difference between `Name` and `AnotherType`. + > - `data Name = NameConstructor AnotherType` does mark a difference. + > - `data` can construct structures which can be recursives. + > - `deriving` is magic and creates functions for you. + +en: In Haskell, types are strong and static. +fr: En Haskell, les types sont forts et statiques. + +en: Why is this important? It will help you _greatly_ to avoid mistakes. +en: In Haskell, most bugs are caught during the compilation of your program. +en: And the main reason is because of the type inference during compilation. +en: Type inference makes it easy to detect where you used the wrong parameter at the wrong place, for example. +fr: Pourquoi est-ce important? Cela vous aidera a éviter _beaucoup_ d'erreurs. +fr: En Haskell, la majorité des bugs est repérée durant la compilation de votre programme. +fr: Et la raison principale de cela est l'inférence de type durant la compilation. +fr: L'inférence de type permet de détecter plus facilement lorsque vous utilisez le mauvais paramètre au mauvais endroit, par exemple + +en:

Type inference

+fr:

Inférence de type

+ +en: Static typing is generally essential for fast execution. +en: But most statically typed languages are bad at generalizing concepts. +en: Haskell's saving grace is that it can _infer_ types. +fr: Le typage statique est généralement essentiel pour une exécution rapide. +fr: Mais la plupart des langages typés statiquement ont du mal à généraliser des concepts. +fr: La "grâce salvatrice" de Haskell est qu'il peut _inférer_ des types. + +en: Here is a simple example, the `square` function in Haskell: +fr: Voici un exemple simple, la fonction `square` en Haskell: + + +square x = x * x + + +en: This function can `square` any Numeral type. +en: You can provide `square` with an `Int`, an `Integer`, a `Float` a `Fractional` and even `Complex`. Proof by example: +fr: Cette fonction peut mettre au carré n'importe quel type `Numeral`. +fr: Vous pouvez l'utilser avec un `Int`, un `Integer`, un `Float`, un `Fractional` ou même un `Complex`. Preuve par l'exemple: + +~~~ +% ghci +GHCi, version 7.0.4: +... +Prelude> let square x = x*x +Prelude> square 2 +4 +Prelude> square 2.1 +4.41 +en: Prelude> -- load the Data.Complex module +fr: Prelude> -- charge le module Data.Complex +Prelude> :m Data.Complex +Prelude Data.Complex> square (2 :+ 1) +3.0 :+ 4.0 +~~~ + +en: `x :+ y` is the notation for the complex (x + iy). +fr: `x :+ y` est la notation pour le complexe (x + iy) + +en: Now compare with the amount of code necessary in C: +fr: Comparons maintenant avec la quantité de code nécessaire pour le faire en 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); + + +en: For each type, you need to write a new function. +en: The only way to work around this problem is to use some meta-programming trick, for example using the pre-processor. +en: In C++ there is a better way, C++ templates: +fr: Pour chaque type, vous avez besoin d'écrire une nouvelle fonction. +fr: Le seul moyen de se débarrasser de ce problème est d'utiliser des astuces de méta-programmation, par exemple en utilisant le pré-processeur. +fr: en C++ il y a un meilleur moyen, les _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; +} + + +en: C++ does a far better job than C in this regard. +en: But for more complex functions the syntax can be hard to follow: +en: see [this article](http://bartoszmilewski.com/2009/10/21/what-does-haskell-have-to-do-with-c/) for example. +fr: C++ fait un bien meilleur travail que C ici. +fr: Mais pour des fonctions plus complexes, la syntaxe sera difficile à suivre. +fr: Voyez [cet article](http://bartoszmilewski.com/2009/10/21/what-does-haskell-have-to-do-with-c/) pour quelques exemples. (_NDT: toujours en anglais) + +en: In C++ you must declare that a function can work with different types. +en: In Haskell, the opposite is the case. +en: The function will be as general as possible by default. +fr: En C++ vous devez déclarer qu'une fonction peut marcher avec différents types. +fr: En Haskell, c'est le contraire. +fr: La fonction sera aussi générale que possible par défaut. + +en: Type inference gives Haskell the feeling of freedom that dynamically typed languages provide. +en: But unlike dynamically typed languages, most errors are caught before run time. +en: Generally, in Haskell: +fr: L'inférence de type donne à Haskell le sentiment de liberté que les langages dynamiquement typés proposent. +fr: Mais contrairement aux langages dynamiquement typés, la majorité des erreurs est détectée avant de lancer le programme. +fr: Généralement, en Haskell: + +en: > "if it compiles it certainly does what you intended" +fr: > "Si ça compile, ça fait certainement ce que vous attendiez." diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/21_Types.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/21_Types.lhs new file mode 100644 index 0000000..8bfb530 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/21_Types.lhs @@ -0,0 +1,19 @@ +en:

Type construction

+fr:

Construction de types

+ +en: You can construct your own types. +en: First, you can use aliases or type synonyms. +fr: Vous pouvez construire vos propres types. +fr: D'abord, vous pouvez utiliser des alias ou des synonymes de types. + +> 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/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/22_Types.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/22_Types.lhs new file mode 100644 index 0000000..e41679b --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/22_Types.lhs @@ -0,0 +1,90 @@ + +en: But it doesn't protect you much. +en: Try to swap the two parameter of `showInfos` and run the program: +fr: Mais cela ne vous protège pas tellement. +fr: Essayez d'inverser les deux paramètres de `showInfos` et lancez le programme: + + + putStrLn $ showInfos color name + + +en: It will compile and execute. +en: In fact you can replace Name, Color and String everywhere. +en: The compiler will treat them as completely identical. +fr: Le code sera compilé et exécuté. +fr: En fait vous pouvez remplace Name, Color et String n'importe où. +fr: Le compilateur les traitera comme si ils était complétement identiques. + +en: Another method is to create your own types using the keyword `data`. +fr: Une autre méthode est de créer vos propres type avec le mot-clé `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 + +en: Now if you switch parameters of `showInfos`, the compiler complains! +en: So this is a potential mistake you will never make again and the only price is to be more verbose. +fr: Maintenant, si vous échangez les paramètres de `showInfos`, le compilateur se plaint! +fr: Au seul prix d'être plus verbeux, vous écartez définitivement cette erreur potentielle. + +en: Also notice that constructors are functions: +fr: Remarquez aussi que les constructeurs sont des fonctions : + + +NameConstr :: String -> Name +ColorConstr :: String -> Color + + +en: The syntax of `data` is mainly: +fr: La syntaxe de `data` est principalement: + + +data TypeName = ConstructorName [types] + | ConstructorName2 [types] + | ... + + +en: Generally the usage is to use the same name for the +en: DataTypeName and DataTypeConstructor. +fr: Généralement on utilise le même nom pour le DatatTypeName et le DataTypeConstructor. + +en: Example: +fr: Exemple : + + +data Complex a = Num a => Complex a a + + +en: Also you can use the record syntax: +fr: Vous pouvez également utiliser cette syntaxe : + + +data DataTypeName = DataConstructor { + field1 :: [type of field1] + , field2 :: [type of field2] + ... + , fieldn :: [type of fieldn] } + + +en: And many accessors are made for you. +en: Furthermore you can use another order when setting values. +fr: Et de nombreux accesseurs sont définis pour vous. +fr: En outre, vous pouvez utiliser une autre ordre lorsque vous définissez des valeurs. + +en: Example: +fr: Exemple : + + +data Complex a = 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/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/23_Types.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/23_Types.lhs new file mode 100644 index 0000000..38de0c0 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/23_Types.lhs @@ -0,0 +1,50 @@ +en:

Recursive type

+fr:

Type récursif

+ +en: You already encountered a recursive type: lists. +en: You can re-create lists, but with a more verbose syntax: +fr: Nous avons déjà rencontré un type récursif : les listes. +fr: Nous pourrions re-créer les listes, avec une syntaxe plus bavarde: + + +data List a = Empty | Cons a (List a) + + + +en: If you really want to use an easier syntax you can use an infix name for constructors. +fr: Si vous voulez réellement utiliser une syntxe plus simple, utilisez un nom infixe pour les constructeurs. + + +infixr 5 ::: +data List a = Nil | a ::: (List a) + + +en: The number after `infixr` gives the precedence. +fr: Le nombre après `infixr` donne la priorité. + +en: 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. +fr: Si vous voulez pouvoir écrire (`Show`), lire (`Read`), tester l'égalité (`Eq`) et comparer (`Ord`) votre nouvelle structure, vous pouvez demander à Haskell de dériver les fonctions appropriées pour vous. + +> infixr 5 ::: +> data List a = Nil | a ::: (List a) +> deriving (Show,Read,Eq,Ord) + +en: When you add `deriving (Show)` to your data declaration, Haskell creates a `show` function for you. +en: We'll see soon how you can use your own `show` function. +fr: Quand vous ajoutez `deriving (Show)` à votre déclaration, Haskell crée une fonction `show` pour vous. +fr: Nous verrons bientôt comment utiliser sa propre fonction `show`. + +> convertList [] = Nil +> convertList (x:xs) = x ::: convertList xs + +> main = do +> print (0 ::: 1 ::: Nil) +> print (convertList [0,1]) + +en: This prints: +fr: Ceci donne : + +~~~ +0 ::: (1 ::: Nil) +0 ::: (1 ::: Nil) +~~~ diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/30_Trees.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/30_Trees.lhs new file mode 100644 index 0000000..3441406 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/30_Trees.lhs @@ -0,0 +1,49 @@ +en:

Trees

+fr:

Les arbres

+ +blogimage("magritte-l-arbre.jpg","Magritte, l'Arbre") + +en: We'll just give another standard example: binary trees. +fr: Voici une autre exemple standard : les arbres binaires. + +> import Data.List +> +> data BinTree a = Empty +> | Node a (BinTree a) (BinTree a) +> deriving (Show) + +en: We will also create a function which turns a list into an ordered binary tree. +fr: Créons aussi une fonctions qui transforme une liste en un arbre binaire ordonné. + +> treeFromList :: (Ord a) => [a] -> BinTree a +> treeFromList [] = Empty +> treeFromList (x:xs) = Node x (treeFromList (filter ( (treeFromList (filter (>x) xs)) + +en: Look at how elegant this function is. +en: In plain English: +fr: Remarquez à quel point cette fonction est élégante. +fr: En français : + +en: - an empty list will be converted to an empty tree. +en: - a list `(x:xs)` will be converted to a tree where: +en: - The root is `x` +en: - Its left subtree is the tree created from members of the list `xs` which are strictly inferior to `x` and +en: - the right subtree is the tree created from members of the list `xs` which are strictly superior to `x`. +fr: - une liste vide est convertie en un arbre vide +fr: - une liste `(x:xs)` sera convertie en un arbre où : +fr: - La racine est `x` +fr: - Le "sous-arbre" de gauche est l'arbre créé à partir des membres de la liste `xs` strictement inférieurs à `x` +fr: - Le "sous-arbre" de droite est l'arbre créé à partir des membres de la liste `xs` strictement superieurs à `x` + +> main = print $ treeFromList [7,2,4,8] + +en: You should obtain the following: +fr: Vous devriez obtenir : + +~~~ +Node 7 (Node 2 Empty (Node 4 Empty Empty)) (Node 8 Empty Empty) +~~~ + +en: This is an informative but quite unpleasant representation of our tree. +fr: C'est une représentation de notre arbre informative mais plutôt déplaisante. diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/31_Trees.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/31_Trees.lhs new file mode 100644 index 0000000..7a90d6a --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/31_Trees.lhs @@ -0,0 +1,231 @@ +en: Just for fun, let's code a better display for our trees. +en: I simply had fun making a nice function to display trees in a general way. +en: You can safely skip this part if you find it too difficult to follow. +fr: Juste pour le plaisir, codons un meilleur affichage pour nos arbres. +fr: Je me suis simplement amusé à faire une belle fonction pour afficher les arbres de façon générale. +fr: Vous pouvez passer cette partie si vous la trouvez difficile à suivre. + +en: We have a few changes to make. +en: We remove the `deriving (Show)` from the declaration of our `BinTree` type. +en: And it might also be useful to make our BinTree an instance of (`Eq` and `Ord`) so we will be able to test equality and compare trees. +fr: Nous avons quelques changements à faire. +fr: Enlevons le `deriving (Show)` de la déclaration de notre type `BinTree`. +fr: Il serait aussi utile de faire de BinTree une instance de (`Eq` et `Ord`), nous serons ainsi capable de tester l'égalité et de comparer des arbres. + +> data BinTree a = Empty +> | Node a (BinTree a) (BinTree a) +> deriving (Eq,Ord) + +en: Without the `deriving (Show)`, Haskell doesn't create a `show` method for us. +en: We will create our own version of `show`. +en: To achieve this, we must declare that our newly created type `BinTree a` +en: is an instance of the type class `Show`. +en: The general syntax is: +fr: Sans le `deriving (Show)`, Haskell ne crée pas de méthode `show` pour nous. +fr: Nous allons créer notre propre version. +fr: Pour accomplir cela, nous devons déclarer que notre type `BinTree a` +fr: est une instance de la classe de type `Show`. +fr: La syntaxe générale est : + + +instance Show (BinTree a) where +en: show t = ... -- You declare your function here +fr: show t = ... -- Déclarez votre fonction ici + + +en: Here is my version of how to show a binary tree. +en: Don't worry about the apparent complexity. +en: I made a lot of improvements in order to display even stranger objects. +fr: Voici ma version pour afficher un arbre binaire. +fr: Ne vous inquiétez pas de sa complexité apparente. +fr: J'ai fait beaucoup d'améliorations pour afficher même les objets les plus étranges. + +> -- 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" + + +en: The `treeFromList` method remains identical. +fr: La méthode `treeFromList` reste identique. + +> treeFromList :: (Ord a) => [a] -> BinTree a +> treeFromList [] = Empty +> treeFromList (x:xs) = Node x (treeFromList (filter ( (treeFromList (filter (>x) xs)) + +en: And now, we can play: +fr: Et maintenant, nous pouvons jouer : + +> main = do +> putStrLn "Int binary tree:" +> print $ treeFromList [7,2,4,8,1,3,6,21,12,23] + +~~~ +en: Int binary tree: +fr: Arbre binaire d'Int: +< 7 +: |--2 +: | |--1 +: | `--4 +: | |--3 +: | `--6 +: `--8 +: `--21 +: |--12 +: `--23 +~~~ + +en: Now it is far better! +en: The root is shown by starting the line with the `<` character. +en: And each following line starts with a `:`. +en: But we could also use another type. +fr: Maintenant c'est beaucoup mieux ! +fr: La racine est montrée en commençant la ligne avec le caractère `<`. +fr: Et chaque ligne suivante est commence par `:`. +fr: Mais nous pourrions aussi utiliser un autre type. + +> putStrLn "\nString binary tree:" +> print $ treeFromList ["foo","bar","baz","gor","yog"] + +~~~ +en: String binary tree: +fr: Arbre binaire de chaînes de caractères +< "foo" +: |--"bar" +: | `--"baz" +: `--"gor" +: `--"yog" +~~~ + +en: As we can test equality and order trees, we can +en: make tree of trees! +fr: Commme nous pouvons tester l'égalité et ordonner des arbres, +fr: nous pouvons aussi faire des arbres d'arbres! + +> putStrLn "\nBinary tree of Char binary trees:" +> print ( treeFromList +> (map treeFromList ["baz","zara","bar"])) + +~~~ +en: Binary tree of Char binary trees: +fr: Arbre binaire d'arbres binaires de Char : +< < 'b' +: : |--'a' +: : `--'z' +: |--< 'b' +: | : |--'a' +: | : `--'r' +: `--< 'z' +: : `--'a' +: : `--'r' +~~~ + +en: This is why I chose to prefix each line of tree display by `:` (except for the root). +fr: C'est pour cela que j'ai choisi de préfixer chaque ligne par un `:` (sauf pour la racine). + +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"] ] + +en: Which is equivalent to +fr: Qui est équivalent à + + +print ( treeFromList ( + map treeFromList + [ map treeFromList ["YO","DAWG"] + , map treeFromList ["I","HEARD"] + , map treeFromList ["I","HEARD"] + , map treeFromList ["YOU","LIKE","TREES"] ])) + + +en: and gives: +fr: et donne : + +~~~ +en: Binary tree of Binary trees of Char binary trees: +fr: Arbre d'arbres d'arbres de Char : +< < < 'Y' +: : : `--'O' +: : `--< 'D' +: : : |--'A' +: : : `--'W' +: : : `--'G' +: |--< < 'I' +: | : `--< 'H' +: | : : |--'E' +: | : : | `--'A' +: | : : | `--'D' +: | : : `--'R' +: `--< < 'Y' +: : : `--'O' +: : : `--'U' +: : `--< 'L' +: : : `--'I' +: : : |--'E' +: : : `--'K' +: : `--< 'T' +: : : `--'R' +: : : |--'E' +: : : `--'S' +~~~ + +en: Notice how duplicate trees aren't inserted; +en: there is only one tree corresponding to `"I","HEARD"`. +en: We have this for (almost) free, because we have declared Tree to be an instance of `Eq`. +fr: Remarquez que les arbres en double ne sont pas insérés. +fr: Il n'y a qu'un seul arbre correspondant à `"I","HEARD"`. +fr: Nous avons ceci presque gratuitement, car nous avons déclaré Tree comme instance de `Eq`. + +en: See how awesome this structure is: +en: We can make trees containing not only integers, strings and chars, but also other trees. +en: And we can even make a tree containing a tree of trees! +fr: Voyez à quel point cette structure est formidable : +fr: Nous pouvons faire des arbres contenant seulement des entiers, des chaînes de caractères, mais aussi d'autres arbres. +fr: Et nous pouvons même faire un arbre contenant un arbre d'arbres! diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/40_Infinites_Structures.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/40_Infinites_Structures.lhs new file mode 100644 index 0000000..e8bf462 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/40_Infinites_Structures.lhs @@ -0,0 +1,59 @@ +en:

Infinite Structures

+fr:

Structures infinies

+ +blogimage("escher_infinite_lizards.jpg","Escher") + +en: It is often said that Haskell is _lazy_. +fr: On dit souvent que Haskell est _paresseux_. + +en: In fact, if you are a bit pedantic, you should say that [Haskell is _non-strict_](http://www.haskell.org/haskellwiki/Lazy_vs._non-strict). +en: Laziness is just a common implementation for non-strict languages. +fr: En fait, si vous êtes un petit peu pédant, vous devriez dire que [Haskell est _non-strict_](http://www.haskell.org/haskellwiki/Lazy_vs._non-strict) (_NDT: En anglais, pour changer_). +fr: La paresse est juste une implémentation commune aux langages non-stricts. + +en: Then what does "not-strict" mean? From the Haskell wiki: +fr: Alors que signifie "non-strict"? D'après le wiki de Haskell : + +en: > Reduction (the mathematical term for evaluation) proceeds from the outside in. +en: > +en: > so if you have `(a+(b*c))` then you first reduce `+` first, then you reduce the inner `(b*c)` +fr: > La réduction (terme mathématique pour "évaluation") procède depuis l'extérieur. +fr: > +fr: > Donc si vous avez `(a+(b*c))`, alors vous réduisez `+` d'abord, puis vous réduisez `(b*c)` + +en: For example in Haskell you can do: +fr: Par exemple en Haskell vous pouvez faire : + +> -- 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 + +en: And it stops. +fr: Et ça s'arrête. + +en: How? +fr: Comment ? + +en: Instead of trying to evaluate `numbers` entirely, +en: it evaluates elements only when needed. +fr: Au lieu d'essayer d'évaluer `numbers` entièrement, +fr: Haskell évalue les éléments seulement lorsque c'est nécessaire. + +en: Also, note in Haskell there is a notation for infinite lists +fr: Remarquez aussi qu'en Haskell, il y a une notation pour les listes infinies + +~~~ +[1..] ⇔ [1,2,3,4...] +[1,3..] ⇔ [1,3,5,7,9,11...] +~~~ + +en: and most functions will work with them. +en: Also, there is a built-in function `take` which is equivalent to our `take'`. +fr: et que la majorité des fonctions fonctionnera avec ces listes. +fr: Il y a aussi une fonction `take` équivalente à notre `take'`. diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/41_Infinites_Structures.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/41_Infinites_Structures.lhs new file mode 100644 index 0000000..f8384ff --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/02_Hard_Part/41_Infinites_Structures.lhs @@ -0,0 +1,173 @@ + +
+ +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" +> + +
+ +en: Suppose we don't mind having an ordered binary tree. +en: Here is an infinite binary tree: +fr: Supposons que nous ne nous préoccupions pas d'avoir une arbre ordonné. +fr: Voici un arbre binaire infini : + +> nullTree = Node 0 nullTree nullTree + +en: A complete binary tree where each node is equal to 0. +en: Now I will prove you can manipulate this object using the following function: +fr: Un arbre complet où chaque noeud est égal à 0. +fr: Maintenant je vais vous prouver que nous pouvons manipuler cet arbre avec la fonction suivante : + +> -- 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 + +en: See what occurs for this program: +fr: Regardez ce qui se passe avec ce programme : + + +main = print $ treeTakeDepth 4 nullTree + + +en: This code compiles, runs and stops giving the following result: +fr: Le code compile, se lance et s'arrête en donnant ce résultat : + +~~~ +< 0 +: |-- 0 +: | |-- 0 +: | | |-- 0 +: | | `-- 0 +: | `-- 0 +: | |-- 0 +: | `-- 0 +: `-- 0 +: |-- 0 +: | |-- 0 +: | `-- 0 +: `-- 0 +: |-- 0 +: `-- 0 +~~~ + +en: Just to heat up your neurones a bit more, +en: let's make a slightly more interesting tree: +fr: Pour nous chauffer encore un peu les neurones, +fr: faisons un arbre plus intéressant : + +> 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) + +en: Another way to create this tree is to use a higher order function. +en: This function should be similar to `map`, but should work on `BinTree` instead of list. +en: Here is such a function: +fr: Un autre moyen de créer cet arbre est d'utiliser une fonction d'ordre supérieur. +fr: Cette fonction devrait être similaire à `map` n, mais devrait travailler sur un `BinTree` au lieu d'une liste. +fr: Voici cette fonction : + +> -- 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) + +en: _Hint_: I won't talk more about this here. +en: If you are interested in the generalization of `map` to other data structures, +en: search for functor and `fmap`. +fr: _NB_: Je ne parlerai pas plus de cette fonction ici. +fr: Si vous vous intéressez à la généralisation de `map` à d'autres structures de données, +fr: cherchez des informations sur les foncteurs et `fmap`. + +en: Our definition is now: +fr: Notre définition est maintenant : + +> infTreeTwo :: BinTree Int +> infTreeTwo = Node 0 (treeMap (\x -> x-1) infTreeTwo) +> (treeMap (\x -> x+1) infTreeTwo) + +en: Look at the result for +fr: Regardez le résultat pour + + +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/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/00_Introduction.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/00_Introduction.lhs new file mode 100644 index 0000000..7186e7a --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/00_Introduction.lhs @@ -0,0 +1,28 @@ +en:

Hell Difficulty Part

+fr:

Partie de difficulté infernale

+ +en: Congratulations for getting so far! +en: Now, some of the really hardcore stuff can start. +fr: Félicitations pour être allé si loin! +fr: Maitenant, les choses vraiment extrêmes peuvent commencer. + +en: If you are like me, you should get the functional style. +en: You should also understand a bit more the advantages of laziness by default. +en: But you also don't really understand where to start in order to make a real +en: program. +en: And in particular: +fr: Si vous êtes comme moi, vous êtes déjà familier avec le style fonctionnel. +fr: Vous devriez également comprendre les avantages de la paresse par défaut. +fr: Mais vous ne comprenez peut-être pas vraiment par où commencer pour faire un vrai +fr: programme. +fr: Et en particulier : + +en: - How do you deal with effects? +en: - Why is there a strange imperative-like notation for dealing with IO? +fr: - Comment s'occuper des effets ? +fr: - Pourquoi y a t-il une étrange notation impérative lorsque l'on s'occupe de l'Entrée/Sortie? (E/S, _IO_ pour _Input/Output_ en anglais) + +en: Be prepared, the answers might be complex. +en: But they are all very rewarding. +fr: Accrochez-vous, les réponses risquent d'être compliquées. +fr: Mais elles en valent la peine. diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/01_progressive_io_example.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/01_progressive_io_example.lhs new file mode 100644 index 0000000..5d64772 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/01_progressive_io_example.lhs @@ -0,0 +1,117 @@ +en:

Deal With IO

+fr:

S'occuper de l'E/S (IO)

+ +blogimage("magritte_carte_blanche.jpg","Magritte, Carte blanche") + + > %tldr + > +en: > A typical function doing `IO` looks a lot like an imperative program: +fr: > Une fonction typique qui fait de l'`IO` ressemble à un programme impératif: + > + > ~~~ + > f :: IO a + > f = do + > x <- action1 + > action2 x + > y <- action3 + > action4 x y + > ~~~ + > +en: > - To set a value to an object we use `<-` . +en: > - The type of each line is `IO *`; +en: > in this example: +fr: > - Pour définir la valeur d'un objet on utilise `<-` . +fr: > - Le type de chaque ligne est `IO *`; +fr: > dans cet exemple: + > - `action1 :: IO b` + > - `action2 x :: IO ()` + > - `action3 :: IO c` + > - `action4 x y :: IO a` + > - `x :: b`, `y :: c` +en: > - Few objects have the type `IO a`, this should help you choose. +en: > In particular you cannot use pure functions directly here. +en: > To use pure functions you could do `action2 (purefunction x)` for example. +fr: > - Quelques objets ont le type `IO a`, cela devrait vous aider à choisir. +fr: > En particulier vous ne pouvez pas utiliser de fonctions pures directement ici. +fr: > Pour utiliser des fonctions pures vous pourriez faire `action2 (pureFunction x)` par exemple. + +en: In this section, I will explain how to use IO, not how it works. +en: You'll see how Haskell separates the pure from the impure parts of the program. +fr: Dans cette section, je vais expliquer comment utiliser l'IO, pas comment ça marche. +fr: Vous verrez comment Haskell sépare les parties pures et impures du programme. + +en: Don't stop because you're trying to understand the details of the syntax. +en: Answers will come in the next section. +fr: Ne vous arrêtez pas sur les détails de la syntaxe +fr: Les réponses viendront dans la section suivante. + +en: What to achieve? +fr: Que cherchons-nous à faire? + +en: > Ask a user to enter a list of numbers. +en: > Print the sum of the numbers +fr: > Demander une liste de nombres à l'utilisateur. +fr: > Afficher la somme de ces nombres. + +> toList :: String -> [Integer] +> toList input = read ("[" ++ input ++ "]") +> +> main = do +> putStrLn "Enter a list of numbers (separated by comma):" +> input <- getLine +> print $ sum (toList input) + +en: It should be straightforward to understand the behavior of this program. +en: Let's analyze the types in more detail. +fr: Il devrait être simple de comprendre le comportement de ce programme. +fr: Analysons les types en détails. + +~~~ +putStrLn :: String -> IO () +getLine :: IO String +print :: Show a => a -> IO () +~~~ + +en: Or more interestingly, we note that each expression in the `do` block has a type of `IO a`. +fr: Ou, de manièree plus intéressante, on remarque que chaque expression dans le bloc `do` est de type `IO a`. + +
+main = do
+  putStrLn "Enter ... " :: IO ()
+  getLine               :: IO String
+  print Something       :: IO ()
+
+ +en: We should also pay attention to the effect of the `<-` symbol. +fr: Nous devrions aussi prêter attention à l'effet du symbole `<-`. + +~~~ +do + x <- something +~~~ + +en: If `something :: IO a` then `x :: a`. +fr: Si `something :: IO a` alors `x :: a`. + +en: Another important note about using `IO`: +en: All lines in a do block must be of one of the two forms: +fr: Une autre remarque importante sur l'`IO`: +fr: Toutes les lignes d'un bloc `do` doivent être d'une des deux formes suivantes : + +~~~ +action1 :: IO a + -- in this case, generally a = () +~~~ + +ou + +~~~ +value <- action2 -- where + -- action2 :: IO b + -- value :: b +~~~ + +en: These two kinds of line will correspond to two different ways of sequencing actions. +en: The meaning of this sentence should be clearer by the end of the next section. +fr: Ces deux types de ligne correspondent à deux types différents de séquençage d'action. +fr: La signification de cette phrase devrait être plus claire à la fin de la prochaine section. diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/02_progressive_io_example.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/02_progressive_io_example.lhs new file mode 100644 index 0000000..4feec04 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/02_progressive_io_example.lhs @@ -0,0 +1,123 @@ +en: Now let's see how this program behaves. +en: For example, what happens if the user enters something strange? +en: Let's try: +fr: Maintenant voyons comment ce programme se comporte. +fr: Par exemple, que ce passe-t-il si l'utilisateur entre une mauvaise valeur? +fr: Essayons : + +~~~ + % runghc 02_progressive_io_example.lhs + Enter a list of numbers (separated by comma): + foo + Prelude.read: no parse +~~~ + + +en: Argh! An evil error message and a crash! +fr: Argh! Un message d'erreur effrayant et un crash ! +en: Our first improvement will simply be to answer with a more friendly message. +fr: Notre première amélioration sera de répondre avec un message plus amical. + +en: In order to do this, we must detect that something went wrong. +fr: Pour faire cela, nous devons détecter que quelque chose s'est mal passé. +en: Here is one way to do this: use the type `Maybe`. +fr: Voici un moyen de le faire : utiliser le type `Maybe`. +en: This is a very common type in Haskell. +fr: C'est un type très utilisé en Haskell. + +> import Data.Maybe + +en: What is this thing? `Maybe` is a type which takes one parameter. +fr: Mais qu'est-ce que c'est ? `Maybe` est un type qui prend un paramètre. +en: Its definition is: +fr: Sa définition est : + + +data Maybe a = Nothing | Just a + + +fr: C'est un bon moyen de dire qu'il y a eu une erreur en essayant de créer/évaluer +en: This is a nice way to tell there was an error while trying to create/compute +fr: une valeur. +en: a value. +fr: La fonction `maybeRead` en est un bon exemple. +en: The `maybeRead` function is a great example of this. +fr: C'est une fonction similaire à `read`[^1], +en: This is a function similar to the function `read`[^1], +fr: mais s'il y a un problème, la valeur retournée est `Nothing`. +en: but if something goes wrong the returned value is `Nothing`. +fr: Si la valeur est bonne, la valeur retournée est `Just `. +en: If the value is right, it returns `Just `. +fr: Ne vous efforcez pas trop de comprendre cette fonction. +en: Don't try to understand too much of this function. +fr: J'utilise une fonction de plus bas niveau que `read` : `reads`. +en: I use a lower level function than `read`: `reads`. + +fr: [^1]: Qui est elle-même très similaire à la fonction `eval` de javascript, appliquée sur une chaîne contenant du code au format JSON. +en: [^1]: Which is itself very similar to the javascript `eval` function, that is applied to a string containing JSON. + +> maybeRead :: Read a => String -> Maybe a +> maybeRead s = case reads s of +> [(x,"")] -> Just x +> _ -> Nothing + +fr: Maintenant, pour être plus lisible, on définit une fonction comme ceci : +en: Now to be a bit more readable, we define a function which goes like this: +fr: Si la chaîne a un mauvais format, elle retournera `Nothing`. +en: If the string has the wrong format, it will return `Nothing`. +fr: Sinon, par exemple pour "1,2,3", cela retournera `Just [1,2,3]`. +en: Otherwise, for example for "1,2,3", it will return `Just [1,2,3]`. + +> getListFromString :: String -> Maybe [Integer] +> getListFromString str = maybeRead $ "[" ++ str ++ "]" + + +en: We simply have to test the value in our main function. +fr: Nous avons juste à tester la valeur dans notre fonction principale. + +> 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." + +fr: En cas d'erreur, on affiche un joli message. +en: In case of error, we display a nice error message. + +fr: Notez que le type de chaque expression dans le bloc `do` de `main` reste de la forme `IO a`. +en: Note that the type of each expression in the main's `do` block remains of the form `IO a`. +fr: La seule construction étrange est `error`. +en: The only strange construction is `error`. +fr: Disons juste que `error msg` prend le type nécessaire (ici, `IO ()`). +en: I'll just say here that `error msg` takes the needed type (here `IO ()`). + +fr: Une chose très importante à noter est le type de toutes les fonctions définies jusqu'ici. +en: One very important thing to note is the type of all the functions defined so far. +fr: Il n'y a qu'une seule fonction qui contient `IO` dans son type : `main`. +en: There is only one function which contains `IO` in its type: `main`. +fr: Cela signifie que `main` est impure. +en: This means main is impure. +fr: Mais `main` utilise `getListFromString`, qui, elle, est pure. +en: But main uses `getListFromString` which is pure. +fr: Nous pouvons donc facilement repérer quelles fonctions sont pures +en: So it's clear just by looking at declared types which functions are pure and +fr: et lesquelles sont impures, seulement en regardant leur type. +en: which are impure. + +fr: Pourquoi la pureté a-t-elle de l'importance? +en: Why does purity matter? +fr: Parmi ses nombreux avantages, en voici trois : +en: Among the many advantages, here are three: + +fr: - Il est beaucoup plus facile de penser à du code pur qu'à du code impur. +en: - It is far easier to think about pure code than impure code. +fr: - La pureté vous protège de tous les bugs difficiles à reproduire dûs aux [effets de bord](https://fr.wikipedia.org/wiki/Effet_de_bord_(informatique)). +en: - Purity protects you from all the hard-to-reproduce bugs that are due to side effects. +fr: - Vous pouvez évaluer des fonctions pures dans n'importe quel ordre ou en parallèle, sans prendre de risques. +en: - You can evaluate pure functions in any order or in parallel without risk. + +fr: C'est pourquoi vous devriez mettre le plus de code possible dans des fonctions pures. +en: This is why you should generally put as most code as possible inside pure functions. diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/03_progressive_io_example.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/03_progressive_io_example.lhs new file mode 100644 index 0000000..8293a83 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/03_progressive_io_example.lhs @@ -0,0 +1,80 @@ +fr: La prochaine étape sera de demander la liste de nombres à l'utilisateur encore et encore jusqu'à ce qu'il entre une réponse valide. +en: Our next iteration will be to prompt the user again and again until she enters a valid answer. + +fr: Nous gardons la première partie : +en: 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 ++ "]" + +fr: Maintenant nous créons la fonction qui demandera une liste d'entiers à l'utilisateur +en: Now we create a function which will ask the user for an list of integers +fr: jusqu'à ce que l'entrée soit correcte +en: 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 + +fr: Cette fonction est de type `IO [Integer]`. +en: This function is of type `IO [Integer]`. +fr: Cela signifie que la valeur récupérée est de type `[Integer`] et est le résultat d'actions d'E/S. +en: Such a type means that we retrieved a value of type `[Integer]` through some IO actions. +fr: D'aucuns diront avec enthousiasme : +en: Some people might explain while waving their hands: + +fr: > «C'est un `[Integer]` dans un `IO` !» +en: > «This is an `[Integer]` inside an `IO`» + +fr: Si vous voulez comprendre les détails derrière tout cela, vous devrez lire la prochaine section. +en: If you want to understand the details behind all of this, you'll have to read the next section. +fr: Mais si vous voulez seulement _utiliser_ l'E/S, contentez-vous pratiquer un peu et rappelez-vous de penser aux types. +en: But really, if you just want to _use_ IO just practice a little and remember to think about the type. + +fr: Finalement, notre fonction `main`est bien plus simple : +en: Finally our main function is much simpler: + +> main :: IO () +> main = do +> list <- askUser +> print $ sum list + +fr: Nous avons fini notre introduction à l'`IO`. +en: We have finished with our introduction to `IO`. +fr: C'était plutôt rapide. Voici les principales choses à retenir : +en: This was quite fast. Here are the main things to remember: + +fr: - Dans le bloc `do`, chaque expression doit avoir le type `IO a`. +en: - in the `do` block, each expression must have the type `IO a`. +fr: Vous êtes donc limité quant au panel d'expression disponibles. +en: You are then limited with regard to the range of expressions available. +fr: Par exemple, `getLine`, `print`, `putStrLn`, etc... +en: For example, `getLine`, `print`, `putStrLn`, etc... +fr: - Essayez d'externaliser le plus possible les fonctions pures. +en: - Try to externalize the pure functions as much as possible. +fr: - le type `IO a` signifie : une _action_ d'E/S qui retourne un élément de type a. +en: - the `IO a` type means: an IO _action_ which returns an element of type `a`. +fr: L'`IO` représente des actions; sous le capot, `IO a` est le type d'une fonction. +en: `IO` represents actions; under the hood, `IO a` is the type of a function. +fr: Lisez la prochaine section si vous êtes curieux. +en: Read the next section if you are curious. + +fr: Si vous pratiquez un peu, vous devriez être capable d'_utiliser_ l'`IO`. +en: If you practice a bit, you should be able to _use_ `IO`. + +fr: > -Exercices_: +en: > _Exercises_: + > +fr: > - Écrivez un programme qui additionne tous ses arguments. Utilisez la fonction `getArgs`. +en: > - Make a program that sums all of its arguments. Hint: use the function `getArgs`. diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/20_Detailled_IO.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/20_Detailled_IO.lhs new file mode 100644 index 0000000..6cfd73f --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/20_Detailled_IO.lhs @@ -0,0 +1,549 @@ +fr:

Le truc des IO révélé

+en:

IO trick explained

+ +blogimage("magritte_pipe.jpg","Magritte, ceci n'est pas une pipe") + +fr: > Voici un %tlal pour cette section. +en: > Here is a %tldr for this section. + > +fr: > Pour séparer les parties pures et impures, +en: > To separate pure and impure parts, +fr: > `main` est définie comme une fonction. +en: > `main` is defined as a function +fr: > qui modifie l'état du monde. +en: > which modifies the state of the world. + > + > ~~~ + > main :: World -> World + > ~~~ + > +fr: > Une fonction aura des effets de bord si elle a ce type. +en: > A function is guaranteed to have side effects only if it has this type. +fr: > Mais regardez cette fonction `main` typique: +en: > 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 + > ~~~ + > +fr: > Nous avons beaucoup d'élements temporaires (ici, `w1`, `w2` et `w3`) +en: > We have a lot of temporary elements (here `w1`, `w2` and `w3`) +fr: > qui doivent être passés à l'action suivante. +en: > which must be passed on to the next action. + > +fr: > Nous créons une fonction `bind` ou `(>>=)`. +en: > We create a function `bind` or `(>>=)`. +fr: > Avec `bind` nous n'avons plus besoin de noms temporaires. +en: > With `bind` we don't need temporary names anymore. + > + > ~~~ + > main = + > action1 >>= action2 >>= action3 >>= action4 + > ~~~ + > +fr: > Bonus: Haskell a du sucre syntaxique : +en: > Bonus: Haskell has syntactical sugar for us: + > + > ~~~ + > main = do + > v1 <- action1 + > v2 <- action2 v1 + > v3 <- action3 v2 + > action4 v3 + > ~~~ + + +fr: Pourquoi avons-nous utilisé cette syntaxe étrange, et quel est exactement le type `IO`? +en: Why did we use this strange syntax, and what exactly is this `IO` type? +fr: Cela peut sembler un peu magique. +en: It looks a bit like magic. + +fr: Pour l'instant, oublions les parties pures de notre programme, et concentrons-nous +en: For now let's just forget all about the pure parts of our program, and focus +fr: sur les parties impures: +en: 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 + + +fr: Première remarque : on dirait de l'impératif. +en: First remark: this looks imperative. +fr: Haskell est assez puissant pour faire sembler impératif du code impur. +en: Haskell is powerful enough to make impure code look imperative. +fr: Par exemple, si vous le vouliez vous pourriez créer une boucle `while` en Haskell. +en: For example, if you wish you could create a `while` in Haskell. +fr: En fait, pour utiliser les `IO`, le style impératif est en général plus approprié. +en: In fact, for dealing with `IO`, an imperative style is generally more appropriate. + +fr: Mais vous devriez avoir remarqué que la notation est inhabituelle. +en: But you should have noticed that the notation is a bit unusual. +fr: Voici pourquoi, en détail. +en: Here is why, in detail. + +fr: Dans un langage impur, l'état du monde peut être vu comme une énorme variable globale cachée. +en: In an impure language, the state of the world can be seen as a huge hidden global variable. +fr: Cette variable cachée est accessible par toutes les fonctions du langage. +en: This hidden variable is accessible by all functions of your language. +fr: Par exemple, vous pouvez lire et écrire dans un fichier avec n'importe quelle fonction. +en: For example, you can read and write a file in any function. +fr: Le fait que le fichier putatif existe ou non est une éventualité qui relève des états possibles que le monde courant peut prendre. +en: Whether a file exists or not is a difference in the possible states that the world can take. + +fr: En Haskell l'état courant du monde n'est pas caché. +en: In Haskell the current state of the world is not hidden. +fr: Au contraire, il est dit _explicitement_ que `main` est une fonction qui change _potentiellement_ l'état du monde. +en: Rather, it is _explicitly_ said that `main` is a function that _potentially_ changes the state of the world. +fr: Son type est donc quelque chose comme : +en: Its type is then something like: + + +main :: World -> World + + +fr: Les fonctions ne sont pas toutes susceptibles de modifier cette variable. +en: Not all functions may access this variable. +fr: Celle qui peuvent la modifier sont impures. +en: Those which have access to this variable are impure. +fr: Les fonctions qui ne peuvent pas agir sur la variable sont pures[^032001]. +en: Functions to which the world variable isn't provided are pure[^032001]. + +fr: [^032001]: Il y a quelques exceptions _peu sûres_ à cette règle. Mais vous ne devriez pas en voir en application réelle, sauf pour le _debugging_. +en: [^032001]: There are some _unsafe_ exceptions to this rule. But you shouldn't see such use in a real application except maybe for debugging purposes. + +fr: Haskell considère l'état du monde comme une variable à passer à `main`. +en: Haskell considers the state of the world as an input variable to `main`. +fr: Mais son type réel est plus proche de celui ci[^032002] : +en: But the real type of main is closer to this one[^032002]: + +fr: [^032002]: Pour les curieux, le vrai type est `data IO a = IO {unIO :: State# RealWorld -> (# State# RealWorld, a #)}`. Tous les `#` ont rapport avec l'optimisation et j'ai échangé quelques champs dans mon exemple. Mais c'est l'idée de base. +en: [^032002]: For the curious ones, the real type is `data IO a = IO {unIO :: State# RealWorld -> (# State# RealWorld, a #)}`. All the `#` has to do with optimisation and I swapped the fields in my example. But this is the basic idea. + + +main :: World -> ((),World) + + +fr: Le type `()` est le type "unit". +en: The `()` type is the unit type. +fr: Rien à voir ici. +en: Nothing to see here. + +fr: Maintenant réécrivons notre fonction `main` avec cela à l'esprit : +en: 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 + + +fr: D'abord, on remarque que toutes les fonctions avec des effets de bord doivent avoir le type : +en: First, we note that all functions which have side effects must have the type: + + +World -> (a,World) + + +fr: où `a` est le type du résultat. +en: where `a` is the type of the result. +fr: Par exemple, une fonction `getChar` aura le type `World -> (Char, World). +en: For example, a `getChar` function should have the type `World -> (Char, World)`. + +fr: Une autre chose à noter est l'astuce pour corriger l'ordre d'évaluation. +en: Another thing to note is the trick to fix the order of evaluation. +fr: En Haskell, pour évaluer `f a b`, vous avez l'embarras du choix : +en: In Haskell, in order to evaluate `f a b`, you have many choices: + +fr: - évaluer d'abord `a` puis `b` puis `f a b` +en: - first eval `a` then `b` then `f a b` +fr: - évaluer d'abord `b` puis `a` puis `f a b` +en: - first eval `b` then `a` then `f a b`. +fr: - évaluer `a` et `b` parallèlement, puis `f a b` +en: - eval `a` and `b` in parallel then `f a b` + +fr: Cela vient du fait que nous avons recours à une partie pure du langage. +en: This is true because we're working in a pure part of the language. + +fr: Maintenant, si vous regardez la fonction `main`, vous voyez tout de suite qu'il faut évaluer la première +en: Now, if you look at the main function, it is clear you must eval the first +fr: ligne avant la seconde, car pour évaluer la seconde ligne vous devez +en: line before the second one since to evaluate the second line you have +fr: utliser un paramètre donné suite à l'évaluation de la première ligne. +en: to get a parameter given by the evaluation of the first line. + +fr: Cette astuce fonctionne très bien. +en: This trick works like a charm. +fr: Le compilateur donnera à chaque étape un pointeur sur l'id du nouveau monde courant. +en: The compiler will at each step provide a pointer to a new real world id. +fr: En réalité, `print` sera évaluée comme suit : +en: Under the hood, `print` will evaluate as: + +fr: - Écrit quelque chose sur l'écran +en: - print something on the screen +fr: - Modifie l'id du monde +en: - modify the id of the world +fr: - renvoyer `((), id du nouveau monde)`. +en: - evaluate as `((),new world id)`. + +fr: Maintenant, si jetez un oeil au style de la fonction `main`, vous remarquerez qu'il est clairement peu commode. +en: Now, if you look at the style of the main function, it is clearly awkward. +fr: Essayons de faire la même chose avec la fonction `askUser` : +en: Let's try to do the same to the `askUser` function: + + +askUser :: World -> ([Integer],World) + + +fr: Avant : +en: 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 + + +fr: Après : +en: 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) + + +fr: C'est similaire, mais peu commode. +en: This is similar, but awkward. +fr: Voyez-vous toutes ces variables temporaires `w?`. +en: Look at all these temporary `w?` names. + +fr: Voici la leçon : une implémentation naïve des IO dans les langages fonctionnels purs serait maladroite ! +en: The lesson is: naive IO implementation in Pure functional languages is awkward! + +fr: Heureusement, il y a un meilleur moyen de résoudre ce problème. +en: Fortunately, there is a better way to handle this problem. +fr: Nous voyons un motif. +en: We see a pattern. +fr: Chaque ligne est de la forme : +en: Each line is of the form: + + +let (y,w') = action x w in + + +fr: Même si pour certaines lignes l'argument `x` n'est pas nécessaire. +en: Even if for some lines the first `x` argument isn't needed. +fr: La sortie est un couple, `(answer, newWorldValue)`. +en: The output type is a couple, `(answer, newWorldValue)`. +fr: Chaque fonction `f` doit avoir un type similaire à : +en: Each function `f` must have a type similar to: + + +f :: World -> (a,World) + + +fr: Et ce n'est pas fini, nous pouvons aussi remarquer que nous suivons toujours le même motif : +en: 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 +... + + +fr: Chaque action peut prendre de 0 à n paramètres. +en: Each action can take from 0 to n parameters. +fr: Et en particulier, chaque action prend comme paramètre le résultat de la ligne précédente. +en: And in particular, each action can take a parameter from the result of a line above. + +fr: Par exemple, nous pourrions aussi avoir : +en: For example, we could also have: + + +let (_,w1) = action1 x w0 in +let (z,w2) = action2 w1 in +let (_,w3) = action3 z w2 in +... + + +fr: Avec, bien entendu, `actionN w :: (World) -> (a,World)`. +en: With, of course: `actionN w :: (World) -> (a,World)`. + +fr: > IMPORTANT: Il y a seulement 2 schémas importants à considérer : +en: > IMPORTANT: there are only two important patterns to consider: + > + > ~~~ + > let (x,w1) = action1 w0 in + > let (y,w2) = action2 x w1 in + > ~~~ + > +fr: > et +en: > and + > + > ~~~ + > let (_,w1) = action1 w0 in + > let (y,w2) = action2 w1 in + > ~~~ + +leftblogimage("jocker_pencil_trick.jpg","Jocker pencil trick") + +fr: Maintenant, préparez-vous pour un petit tour de magie ! +en: Now, we will do a magic trick. +fr: Faisons disparaître les variables temporaires de monde courant. +en: We will make the temporary world symbols "disappear". +fr: Nous allons `attacher` (_NDT: `bind` en anglais_) les deux lignes. +en: We will `bind` the two lines. +fr: Définissons la fonction `bind`. +en: Let's define the `bind` function. +fr: Son type est assez intimidant au début : +en: Its type is quite intimidating at first: + + +bind :: (World -> (a,World)) + -> (a -> (World -> (b,World))) + -> (World -> (b,World)) + + +fr: Mais gardez en tête que `(World -> (a,World))` est le type d'une action d'IO. +en: But remember that `(World -> (a,World))` is the type for an IO action. +fr: Renommons-le pour plus de clarté : +en: Now let's rename it for clarity: + + +type IO a = World -> (a, World) + + +fr: Quelques exemples de fonctions : +en: Some examples of functions: + + +getLine :: IO String +print :: Show a => a -> IO () + + +fr: `getLine` est une action d'E/S qui prend le monde en paramètre et retourne un couple `(String, World)`. +en: `getLine` is an IO action which takes world as a parameter and returns a couple `(String, World)`. +fr: Cela peut être résumé par : `getLine` est de type `IO String`, que nous pouvons voir comme une action d'E/S qui retournera une chaîne de caractères "dans une E/S". +en: This 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". + +fr: La fonction `print` est elle aussi intéressante. +en: The function `print` is also interesting. +fr: Elle prend un argument qui peut être montré avec `show`. +en: It takes one argument which can be shown. +fr: En fait, elle prend deux arguments. +en: In fact it takes two arguments. +fr: Le premier est la valeur et le deuxième est l'état du monde. +en: The first is the value to print and the other is the state of world. +fr: Elle retourne un couple de type `((), World)`. +en: It then returns a couple of type `((), World)`. +fr: Cela signifie qu'elle change l'état du monde, mais ne produit pas d'autre donnée. +en: This means that it changes the state of the world, but doesn't yield any more data. + +fr: Ce nouveau type `IO a` nous aide à simplifier le type de `bind` : +en: This new `IO a` type helps us simplify the type of `bind`: + + +bind :: IO a + -> (a -> IO b) + -> IO b + + +fr: Cela dit que `bind` prend deux actions d'E/S en paramètres et retourne une autre action d'E/S. +en: It says that `bind` takes two IO actions as parameters and returns another IO action. + +fr: Maintenant, rappelez-vous des motifs _importants_. Le premier était : +en: Now, remember the _important_ patterns. The first was: + + +pattern1 w0 = + let (x,w1) = action1 w0 in + let (y,w2) = action2 x w1 in + (y,w2) + + +fr: Voyez les types : +en: Look at the types: + + +action1 :: IO a +action2 :: a -> IO b +pattern1 :: IO b + + +fr: Cela ne vous semble-t-il pas familier ? +en: Doesn't it seem familiar? + + +(bind action1 action2) w0 = + let (x, w1) = action1 w0 + (y, w2) = action2 x w1 + in (y, w2) + + +fr: L'idée est de cacher l'argument `World` avec cette fonction. Allons-y ! +en: The idea is to hide the World argument with this function. Let's go: +fr: Par exemple si nous avions voulu simuler : +en: As an example imagine if we wanted to simulate: + + +let (line1, w1) = getLine w0 in +let ((), w2) = print line1 in +((), w2) + + +fr: Maintenant, en utilisant la fonction `bind` : +en: Now, using the `bind` function: + + +(res, w2) = (bind getLine print) w0 + + +fr: Comme `print` est de type `Show a => a -> (World -> ((), World))`, nous savons que `res = ()` (type `unit`) +en: As print is of type `Show a => a -> (World -> ((), World))`, we know `res = ()` (`unit` type). +fr: Si vous ne voyez pas ce qui est magique ici, essayons avec trois lignes cette fois. +en: 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) + + +fr: Qui est équivalent à : +en: Which is equivalent to: + + +(res,w3) = (bind getLine (\line1 -> + (bind getLine (\line2 -> + print (line1 ++ line2))))) w0 + + +fr: Avez-vous remarqué quelque chose ? +en: Didn't you notice something? +fr: Oui, aucune variable `World` temporaire n'est utilisée ! +en: Yes, no temporary World variables are used anywhere! +fr: C'est _MA_._GIQUE_. +en: This is _MA_. _GIC_. + +fr: Nous pouvons utiliser une meilleure notation. +en: We can use a better notation. +fr: Utilisons `(>>=)` au lieu de `bind`. +en: Let's use `(>>=)` instead of `bind`. +fr: `(>>=)` est une fonction infixe, comme +en: `(>>=)` is an infix function like +fr: `(+)`; pour mémoire : `3 + 4 ⇔ (+) 3 4` +en: `(+)`; reminder `3 + 4 ⇔ (+) 3 4` + + +(res,w3) = (getLine >>= + (\line1 -> getLine >>= + (\line2 -> print (line1 ++ line2)))) w0 + + +fr: Ho Ho Ho! Joyeux Noël ! +fr; Haskell a confectionné du sucre syntaxique pour vous : +en: Ho Ho Ho! Merry Christmas Everyone! +en: Haskell has made syntactical sugar for us: + + +do + x <- action1 + y <- action2 + z <- action3 + ... + + +fr: Est remplacé par : +en: Is replaced by: + + +action1 >>= (\x -> +action2 >>= (\y -> +action3 >>= (\z -> +... +))) + + +fr: Remarquez que vous pouvez utliser `x` dans `action2` et `x` et `y` dans `action3`. +en: Note that you can use `x` in `action2` and `x` and `y` in `action3`. + +fr: Mais que se passe-t-il pour les lignes qui n'utilisent pas le `<-` ? +en: But what about the lines not using the `<-`? +fr: Facile, une autre fonction `blindBind` : +en: Easy, another function `blindBind`: + + +blindBind :: IO a -> IO b -> IO b +blindBind action1 action2 w0 = + bind action (\_ -> action2) w0 + + +fr: Je n'ai pas simplifié cette définition pour plus de clarté. +en: I didn't simplify this definition for the purposes of clarity. +fr: Bien sûr, nous pouvons utiliser une meilleure notation avec l'opérateur `(>>)`. +en: Of course, we can use a better notation: we'll use the `(>>)` operator. + +fr: Et +en: And + + +do + action1 + action2 + action3 + + +fr: Devient +en: Is transformed into + + +action1 >> +action2 >> +action3 + + +fr: Enfin, une autre fonction est plutôt utile. +en: Also, another function is quite useful. + + +putInIO :: a -> IO a +putInIO x = IO (\w -> (x,w)) + + +fr: D'une manière générale, c'est une façon de mettre des valeurs pures dans le "contexte d'E/S". +en: This is the general way to put pure values inside the "IO context". +fr: Le nom général pour `putInIO` est `return`. +en: The general name for `putInIO` is `return`. +fr: C'est un plutôt un mauvais nom lorsque vous commencez à programmer en Haskell. `return` est très différent de ce à quoi vous pourriez être habitué. +en: This is quite a bad name when you learn Haskell. `return` is very different from what you might be used to. diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/21_Detailled_IO.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/21_Detailled_IO.lhs new file mode 100644 index 0000000..f0c9dee --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/01_IO/21_Detailled_IO.lhs @@ -0,0 +1,49 @@ +fr: Pour finir, traduisons notre exemple : +en: 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 + + +fr: Se traduit en : +en: 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 + +fr: Vous pouvez compiler ce code pour vérifier qu'il marche. +en: You can compile this code to verify that it works. + +fr: Imaginez à quoi il ressemblerait sans le `(>>)` et `(>>=)`. +en: Imagine what it would look like without the `(>>)` and `(>>=)`. diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/10_Monads.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/10_Monads.lhs new file mode 100644 index 0000000..b658ee8 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/10_Monads.lhs @@ -0,0 +1,124 @@ +fr:

Les monades

+en:

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 think about it, I find this disgusting and that wasn't the purpose of this document.") + +fr: Maintenant le secret peut être dévoilé : `IO` est une _monade_. +en: Now the secret can be revealed: `IO` is a _monad_. +fr: Être une monade signifie que vous avez accès à du sucre syntaxique avec la notation `do`. +en: Being a monad means you have access to some syntactical sugar with the `do` notation. +fr: Mais principalement, vous avez accès à un motif de codage qui tempérera le flux de votre code. +en: But mainly, you have access to a coding pattern which will ease the flow of your code. + +fr: > **Remarques importantes** : +en: > **Important remarks**: + > +fr: > - Le monades n'ont pas forcément quoi que ce soit à voir avec les effets de bord ! +en: > - Monad are not necessarily about effects! +fr: > Il y a beaucoup de monades _pures_. +en: > There are a lot of _pure_ monads. +fr: > - Les monades concernent plus le séquençage. +en: > - Monad are more about sequencing + +fr: En Haskell, `Monad` est une classe de type. +en: In Haskell, `Monad` is a type class. +fr: Pour être une instance d'une classe de type, vous devez fournir les fonctions `(>>=)` et `return`. +en: To be an instance of this type class, you must provide the functions `(>>=)` and `return`. +fr: La fonction `(>>)` est dérivée de `(>>=)`. +en: The function `(>>)` is derived from `(>>=)`. +fr: Voici commment la classe de type `Monad` est déclarée (grosso modo) : +en: Here is how the type class `Monad` is declared (basically): + + +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 + +fr: -- Vous pouvez ignorer cette fonction généralement, +en: -- You should generally safely ignore this function +fr: -- je crois qu'elle existe pour des raisons historiques +en: -- which I believe exists for historical reasons + fail :: String -> m a + fail = error + + + +fr: > Remarques : +en: > Remarks: + > +fr: > - le mot-clé `class` n'est pas votre ami. +en: > - the keyword `class` is not your friend. +fr: > Une classe en Haskell _n'est pas_ du même genre que celle des langages orientés-objet. +en: > A Haskell class is _not_ a class of the kind you will find in object-oriented programming. +fr: > Elles ont beaucoup de similarités avec les interfaces de Java. +en: > A Haskell class has a lot of similarities with Java interfaces. +fr: > Un meilleur mot aurait été `typeClass`, ce qui signifierait un ensemble de types. +en: > A better word would have been `typeclass`, since that means a set of types. +fr: > Pour qu'un type appartienne à une classe, toutes les fonctions de cette classe doivent être fournies pour ce type. +en: > For a type to belong to a class, all functions of the class must be provided for this type. +fr: > - Dans cet exemple particulier de classe de type, le type `m` doit être un type qui prend un argument. +en: > - In this particular example of type class, the type `m` must be a type that takes an argument. +fr: > par exemple `IO a`, mais aussi `Maybe a`, `[a]`, etc... +en: > for example `IO a`, but also `Maybe a`, `[a]`, etc... +fr: > - Pour être une monade utile, votre fonction doit obéir à quelques règles. +en: > - To be a useful monad, your function must obey some rules. +fr: > Si votre construction n'obéit pas à ces règles, des choses étranges pourraient se produire : +en: > 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 + > ~~~ + +fr:

Maybe est une monade

+en:

Maybe is a monad

+ +fr: Il y a beaucoup de types différents qui sont des instances de `Monad`. +en: There are a lot of different types that are instances of `Monad`. +fr: L'un des plus faciles à décrire est `Maybe`. +en: One of the easiest to describe is `Maybe`. +fr: Si vous avez une séquence de valeurs `Maybe`, vous pouvez utiliser les monades pour les manipuler. +en: If you have a sequence of `Maybe` values, you can use monads to manipulate them. +fr: C'est particulièrement utile pour enlever des constructions `if..then..else..` trop nombreuses. +en: It is particularly useful to remove very deep `if..then..else..` constructions. + +fr: Imaginez une opération bancaire complexe. Vous êtes éligible pour gagner 700€ seulement si +en: Imagine a complex bank operation. You are eligible to gain about 700€ only +fr: vous pouvez effectuer une liste d'opérations sans tomber en dessous de zéro. +en: if you can afford to follow a list of operations without your balance dipping below zero. + +> 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/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/11_Monads.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/11_Monads.lhs new file mode 100644 index 0000000..40efd31 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/11_Monads.lhs @@ -0,0 +1,23 @@ +fr: Maintenant, améliorons cela en utilisant le fait que `Maybe` est une Monade. +en: 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/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/12_Monads.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/12_Monads.lhs new file mode 100644 index 0000000..f0871c5 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/12_Monads.lhs @@ -0,0 +1,66 @@ +fr: Pas mauvais, mais nous pouvons faire encore mieux : +en: 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 + +fr: Nous avons prouvé que les monades sont un bon moyen de rendre notre code plus élégant. +en: We have proven that Monads are a good way to make our code more elegant. +fr: Remarquez que cette idée d'organisation de code, en particulier pour `Maybe`, peut être utilisée +en: Note this idea of code organization, in particular for `Maybe` can be used +fr: dans la plupart des langages impératifs. +en: in most imperative languages. +fr: En fait, c'est le type de construction que nous faisons naturellement. +en: In fact, this is the kind of construction we make naturally. + +fr: > Une remarque importante : +en: > An important remark: + > +fr: > Le premier élement de la séquence qui sera évalué comme `Nothing` stoppera +en: > The first element in the sequence being evaluated to `Nothing` will stop +fr: > l'évaluation. +en: > the complete evaluation. +fr: > Cela signifie que vous n'exécutez pas toutes les lignes. +en: > This means you don't execute all lines. +fr: > Cela découle du caractère paresseux de Haskell. +en: > You get this for free, thanks to laziness. + +fr: Vous pourriez aussi revoir ces exemples avec la définition de `(>>=)` pour `Maybe` +en: You could also replay these example with the definition of `(>>=)` for `Maybe` +fr: en tête : +en: in mind: + + +instance Monad Maybe where + (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b + Nothing >>= _ = Nothing + (Just x) >>= f = f x + + return x = Just x + + + +fr: La monade `Maybe` a prouvé par un simple exemple qu'elle est utile. +en: The `Maybe` monad proved to be useful while being a very simple example. +fr: Nous avons vu l'utilité de la monade `IO`. +en: We saw the utility of the `IO` monad. +fr: Mais maintenant, voici un exemple encore plus cool : les listes. +en: But now for a cooler example, lists. diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/13_Monads.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/13_Monads.lhs new file mode 100644 index 0000000..e32e638 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/03_Hell/02_Monads/13_Monads.lhs @@ -0,0 +1,67 @@ +fr:

La monade List

+en:

The list monad

+ +blogimage("golconde.jpg","Golconde de Magritte") + +fr: La monade `List` nous aide à simuler des calculs non-déterministes. +en: The list monad helps us to simulate non-deterministic computations. +fr: C'est parti : +en: 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 + + +fr: Ma. GIQUE. : +en: MA. GIC. : + +~~~ +[(1,1,7),(1,1,8),(1,1,9),(1,1,10),(1,2,9),(1,2,10)] +~~~ + +fr: Pour la monade `List`, il y a aussi un sucre syntaxique : +en: For the list monad, there is also this syntactic sugar: + +> print $ [ (x,y,z) | x <- allCases, +> y <- allCases, +> z <- allCases, +> 4*x + 2*y < z ] + +fr: Je ne listerai pas toutes les monades, car il y en a beaucoup. +en: I won't list all the monads, since there are many of them. +fr: Utiliser les monades simplifie la manipulations de plusieurs notions dans les langages purs. +en: Using monads simplifies the manipulation of several notions in pure languages. +fr: Les monades sont très utiles, en particulier pour : +en: In particular, monads are very useful for: + +fr: - L'E/S (IO), +en: - IO, +fr: - les calculs non-déterministes, +en: - non-deterministic computation, +fr: - générer des nombres pseudo-aléatoires, +en: - generating pseudo random numbers, +fr: - garder un état de configuration, +en: - keeping configuration state, +fr: - écrire un état, +en: - writing state, +- ... + +fr: Si vous m'avez suivi jusqu'ici, alors vous avez terminé ! +en: If you have followed me until here, then you've done it! +fr: Vous connaissez les monades[^03021301] ! +en: You know monads[^03021301]! + +fr: [^03021301]: Vous aurez quand même besoin de pratiquer un peu pour vous habituer à elles et pour comprendre quand les utiliser et créer les vôtres. Mais vous avez déjà fait un grand pas dans cette direction. +en: [^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/src/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/00_appendix.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/00_appendix.lhs new file mode 100644 index 0000000..61f2030 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/00_appendix.lhs @@ -0,0 +1,7 @@ +fr:

Appendice

+en:

Appendix

+ +fr: Cette section n'est pas vraiment sur l'apprentissage d'Haskell. +en: This section is not so much about learning Haskell. +fr: Elle est ici pour discuter de quelques détails. +en: It is just here to discuss some details further. diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs b/src/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..7d1f01f --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs @@ -0,0 +1,160 @@ +fr:

Revenons sur les arbres infinis

+en:

More on Infinite Tree

+ +fr: Dans la section sur [les structures infinies](#infinite-structures) nous avons vu quelques +en: In the section [Infinite Structures](#infinite-structures) we saw some simple +fr: constructions simples. +en: constructions. +fr: Malheureusement, nous avons enlevé deux propriétés de notre arbre: +en: Unfortunately we removed two properties from our tree: + +fr: 1. Pas de valeurs identiques +en: 1. no duplicate node value +fr: 2. Arbre bien ordonné +en: 2. well ordered tree + +fr: Dans cette section nous allons tenter de garder la première propriété. +en: In this section we will try to keep the first property. +fr: Concernant la seconde, nous ne devons pas nous en préoccuper ici mais nous discuterons +en: Concerning the second one, we must relax it but we'll discuss how to +fr: de comment la garder le plus possible. +en: 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" +> + +
+ +fr: Notre première étape est de créer une liste de nombres pseudo-aléatoires: +en: Our first step is to create some pseudo-random number list: + +> shuffle = map (\x -> (x*3123) `mod` 4331) [1..] + +fr: Pour mémoire, voici la définition de `treeFromList` +en: 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)) + +fr: et +en: 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 + +fr: Voyez le résultats de: +en: 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 +~~~ + +fr: Le code fonctionne! +en: Yay! It ends! +fr: Attention cependant, cela marchere seulement si vous avez toujours quelque chose à mettre dans une branche. +en: Beware though, it will only work if you always have something to put into a branch. + +fr: Par exemple +en: For example + + +treeTakeDepth 4 (treeFromList [1..]) + + +fr: tournera en boucle pour toujours. +en: will loop forever. +fr: Simplement parce que le code essayera d'accéder à première valeur de `filter (<1) [2..]`. +en: Simply because it will try to access the head of `filter (<1) [2..]`. +fr: Mais `filter` n'est pas assez intelligent pour comprendre que le résultat est une liste vide. +en: But `filter` is not smart enought to understand that the result is the empty list. + +fr: Toutefois, cela reste un exemple sympa de ce qu'un programme non-stricit a à offrir. +en: Nonetheless, it is still a very cool example of what non strict programs have to offer. + +fr: Laissé pour exercice au lecteur: +en: Left as an exercise to the reader: + +fr: - Prouver l'existence d'un nombre `n` tel que `treeTakeDepth n (treeFromList shuffle)` provoquera une boucle infinie. +en: - Prove the existence of a number `n` so that `treeTakeDepth n (treeFromList shuffle)` will enter an infinite loop. +fr: - Trouver une borne supérieur `n`. +en: - Find an upper bound for `n`. +fr: - Prouver qu'il n(y a pas de liste `shuffle` qui termine le programme pour n'importe quelle profondeur. +en: - Prove there is no `shuffle` list so that, for any depth, the program ends. diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs b/src/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..13e2b44 --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs @@ -0,0 +1,192 @@ + +
+ +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 + +
+ +fr: Pour résoudre ces problèmes nous allons modifier légèrement nos +en: In order to resolve these problem we will modify slightly our +fr: fonctions `treeFromList` et `shuffle`. +en: `treeFromList` and `shuffle` function. + +fr: Un premier problème est le manque de nombres différents dans notre immlémentation de `shuffle`. +en: A first problem, is the lack of infinite different number in our implementation of `shuffle`. +fr: Nous avons généré seulement `4331` nombres différents. +en: We generated only `4331` different numbers. +fr: Pour résoudre cela nous allons faire un meilleure fonction `shuffle`. +en: 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 + +fr: Cette fonction à la propriété de ne pas avoir de bornes supérieure ou inférieure. +en: This shuffle function has the property (hopefully) not to have an upper nor lower bound. +fr: Mais avoir une meilleure list `shuffle` n'est pas assez pour entrer dans une boucle infinie. +en: But having a better shuffle list isn't enough not to enter an infinite loop. + +fr: Généralement, nous ne pouvons pas décider que `filter ( Tous les élements de la branche de gauche doit être strictement inférieur au la valeur racine. +en: > Any element of the left (resp. right) branch must all be strictly inferior (resp. superior) to the label of the root. + +fr: Remarquez que cela donnera _souvent_ un arbre ordonné. +en: Remark it will remains _mostly_ an ordered binary tree. +fr: En outre, avec cette construction, chaque noeud est unique dans l'arbre. +en: Furthermore, by construction, each node value is unique in the tree. + +fr: Voici notre nouvelle version de `treeFromList`. Nous avons simplement remplacé `filter` par `safefilter`. +en: 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 + +fr: Cette nouvelle fonction `safefilter` est presque équivalente à `filter` mais n'entre pas dans des boucles infinies si le résultat est une liste finie. +en: This new function `safefilter` is almost equivalent to `filter` but don't enter infinite loop if the result is a finite list. +fr: Si elle ne peut pas trouver un élément pour lequel le test est vrai après 10000 étapes consécutives, alors elle considère que la recherche est finie. +en: 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) + +fr: Maintenant faites tourner le programme et soyez heureux: +en: 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) + +fr: Vous devriez réaliser que le temps nécessaire pour afficher chaque valeur est différent. +en: You should realize the time to print each value is different. +fr: C'est parce que Haskell calcule chaque valeur lorsqu'il en a besoin. +en: This is because Haskell compute each value when it needs it. +fr: Et dans ce cas, il est demandé de l'afficher à l'écran. +en: And in this case, this is when asked to print it on the screen. + +fr: Vous pouvez même essayer de remplacer la profondeur de `8` par `100`. +en: Impressively enough, try to replace the depth from `8` to `100`. +fr: Cela marchera sans tuer votre RAM! +en: It will work without killing your RAM! +fr: La gestion de la mémoire est faite naturellement par Haskell. +en: The flow and the memory management is done naturally by Haskell. + +fr: Laissé comme exercices au lecteur: +en: Left as an exercise to the reader: + +fr: - Même avec une grande valeur constante pour `deep` et `nbTry`, cela semble marcher correctement. Mais dans le pire des cas, cela peut devenir exponentiel. +en: - Even with large constant value for `deep` and `nbTry`, it seems to work nicely. But in the worst case, it can be exponential. +fr: Créez la pire liste à donner comme paramètre à `treeFromList`. +en: Create a worst case list to give as parameter to `treeFromList`. +fr: _indice_: pensez à (`[0,-1,-1,....,-1,1,-1,...,-1,1,...]`). +en: _hint_: think about (`[0,-1,-1,....,-1,1,-1,...,-1,1,...]`). +fr: - J'ai commencé à implémenter `safefilter` comme ceci: +en: - I first tried to implement `safefilter` as follow: +
+  safefilter' f l = if filter f (take 10000 l) == []
+                    then []
+                    else filter f l
+  
+fr: Expliquer pourquoi cela ne fonctionne pas et peut entrer dans une boucle infinie. +en: Explain why it doesn't work and can enter into an infinite loop. +fr: - Supposez que `shuffle` est une liste de nombre réellement aléatoires avec de plus en plus de bornes. +en: - Suppose that `shuffle` is real random list with growing bounds. +fr: Si vous étudiez un peu cette structure, vous découvrirez qu'elle a toutes les chances +en: If you study a bit this structure, you'll discover that with probability 1, +fr: d'être finie. +en: this structure is finite. +fr: En utilisant le code suivant +en: Using the following code +fr: (supposez que nous pouvons utliser `safefilter'` directement comme si cela n'était pas dans le `where` de `safefilter`. +en: (suppose we could use `safefilter'` directly as if was not in the where of safefilter) +fr: trouvez une définition de `f` telle que, avec une probabilité de `1`, +en: find a definition of `f` such that with probability `1`, +fr: `treeFromList' shuffle` est infinie?. Et prouvez-le. +en: `treeFromList' shuffle` is infinite. And prove it. +fr: Avertissement, ce n'est qu'une conjecture. +en: 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/src/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/02_Thanks/10_Thanks.lhs b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/02_Thanks/10_Thanks.lhs new file mode 100644 index 0000000..7e11e0c --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/code/04_Appendice/02_Thanks/10_Thanks.lhs @@ -0,0 +1,15 @@ +en: ## Thanks +fr: ## Remerciements + +fr: Merci à [`/r/haskell`](http://reddit.com/r/haskell) et +en: Thanks to [`/r/haskell`](http://reddit.com/r/haskell) and +[`/r/programming`](http://reddit.com/r/programming). +fr: Vos commentaires étaient plus que bienvenus. +en: Your comment were most than welcome. + +fr: Particulièrement, je voudrais remercier mille fois [Emm](https://github.com/Emm) +en: Particularly, I want to thank [Emm](https://github.com/Emm) a thousand times +fr: pour le temps qu'il a consacré à corriger mon anglais. +en: for the time he spent on correcting my English. +fr: Merci beaucoup. +en: Thank you man. diff --git a/src/Scratch/en/blog/Haskell-the-Hard-Way/index.html b/src/Scratch/en/blog/Haskell-the-Hard-Way/index.html new file mode 100644 index 0000000..455db6e --- /dev/null +++ b/src/Scratch/en/blog/Haskell-the-Hard-Way/index.html @@ -0,0 +1,2388 @@ + + + + + YBlog - Learn Haskell Fast and Hard + + + + + + + + + + + + + + + + +
+ + +
+

Learn Haskell Fast and Hard

+

Blow your mind with Haskell

+ +
+
+
+
+
+Magritte pleasure principle +
+ +
+

I really believe all developers should learn Haskell. I don’t think everyone needs to be super Haskell ninjas, but they should at least discover what Haskell has to offer. Learning Haskell opens your mind.

+

Mainstream languages share the same foundations:

+
    +
  • variables
  • +
  • loops
  • +
  • pointers1
  • +
  • data structures, objects and classes (for most)
  • +
+

Haskell is very different. The 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.

+

In contrast, this article is a very brief and dense overview of all major aspects of Haskell. I also added some information 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 see a separator with a filename ending in .lhs you can 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 +

+
+ +
+

There are different way to install Haskell, I would recommend to use stack.

+

There are other way to install Haskell on your system you could visit, you can learn more about it by visiting haskell.org or haskell-lang.org

+

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 books/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”.

+ +

To run it, you can save this code in a hello.hs and:

+ +

or if you use stack first run stack setup and then:

+ +

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:

+ +

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:

+ +

First, let us compare this with similar programs in a few imperative languages:

+ + + +

The structure is the same, but there are some syntax differences. The main 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 they 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 your code is 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 about 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 declaring functions like this:

+

In C:

+ +

In JavaScript:

+ +

in Python:

+ +

in Ruby:

+ +

In Scheme:

+ +

Finally, the Haskell way is:

+ +

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 out for these objects.

+

+A Type Example +

+

Although it is not mandatory, type information for functions is usually made explicit. It’s not mandatory because the compiler is smart enough to discover it for you. It’s a good idea because it indicates intent and understanding.

+

Let’s play a little. We declare the type using ::

+ +
~ runhaskell 20_very_basic.lhs
+13
+

01_basic/10_Introduction/20_very_basic.lhs

+
+

01_basic/10_Introduction/21_very_basic.lhs

+

Now try

+ +

You should 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 a type for f for the moment and let Haskell infer the most general type for us:

+ +

It works! Luckily, 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 typeIts meaning
Intthe type Int
Int -> Intthe type function from Int to Int
Float -> Intthe type function from Float to Int
a -> Intthe type function from any type to Int
a -> athe type function from any type a to the same type a
a -> a -> athe 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 values. For example Int, Integer, Float

+

So instead of having a forced type like in C and having to declare a function for int, long, float, double, etc., we declare only one function like in a dynamically typed language.

+

This is sometimes called parametric polymorphism. It’s also called having your cake and eating it too.

+

Generally a can be any type, for example a String or 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 which 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 a 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 functions. We could also 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 be starting 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:

+ +

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:

+ +

The compiler complains. The two parameters must have the same type.

+

If you believe that this is a bad idea, and that the compiler should make the transformation from one 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 that you skim this part. Think of it as a reference. Haskell has a lot of features. A lot of information is missing here. Come back here if the 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 the return value of an expression is.

+

+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 has 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 a stream of ASCII char, you should use Data.ByteString.

+
+
+Tuples +
+

The type of couple is (a,b). Elements in a tuple can have different types.

+
-- All these tuples 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
+

Remember that 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

+ +

Note ^ uses infix notation. For each infix operator there its associated prefix notation. You just have to put it inside parenthesis.

+ +

We can remove x in the left and right side! It’s called η-reduction.

+ +

Note we can declare functions with ' in their name. Here:

+
+

squaresquare'square''square'''

+
+

Tests

+

An implementation of the absolute function.

+ +

Note: the if .. then .. else Haskell notation is more like the ¤?¤:¤ C operator. You cannot forget the else.

+

Another equivalent version:

+ +
+

Notation warning: indentation is important in Haskell. Like in Python, bad indentation can break your code!

+
+ +

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 in 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 functional and imperative approaches, I’ll start by providing an imperative solution (in JavaScript):

+ +

In Haskell, by contrast, we don’t have variables or a 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 this 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.

+ +

Keep this code in mind. We will translate it into Haskell. First, however, I need to introduce three simple but useful functions we will use:

+ +

even verifies if a number is even.

+ +

head returns the first element of a list:

+ +

tail returns all elements of a list, except the first:

+ +

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:

+ +

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]
+2 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 fact, many things can be improved here. First, we can generalize the type.

+ + +

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 namespace of our module.

+ + +

02_Hard_Part/12_Functions.lhs

+
+

02_Hard_Part/13_Functions.lhs

+

Next, we can use pattern matching.

+ +

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:

+ +

But pattern matching goes even further. It is also able to inspect the inner data of a complex value. We can replace

+ +

with

+ +

This is a very useful feature. It makes our code both terser and easier to read.

+ +

02_Hard_Part/13_Functions.lhs

+
+

02_Hard_Part/14_Functions.lhs

+

In Haskell you can simplify function definitions by η-reducing them. For example, instead of writing:

+ +

you can simply write

+ +

We use this method to remove the l:

+ + +

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 parameters.

+

Here are some examples:

+ +

Let’s proceed by small steps.

+ +

where

+ +

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 technique to accomplish the same thing as a loop. We will use the foldl function to accumulate a value as we pass through the list. 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:

+ + +

But as Haskell is lazy, it doesn’t evaluate (f z x) and simply pushes it onto 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' were identical.

+

Now our new version of evenSum becomes:

+ +

We can also simplify this by using directly a lambda notation. This way we don’t have to create the temporary name mysum.

+ +

And of course, we note that

+ + +

02_Hard_Part/15_Functions.lhs

+
+

02_Hard_Part/16_Functions.lhs

+

Finally

+ +

foldl' isn’t the easiest function to grasp. If you are not used to it, you should study it a bit.

+

To help you understand what’s going on here, let’s look at 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 mathematical composition.

+ +

We can take advantage of this operator to η-reduce our function:

+ +

Also, we could rename some parts to make it clearer:

+ +

It is time to discuss the direction our code has moved as we introduced more functional idioms. What did we gain by using higher order functions?

+

At first, you might think the main difference is terseness. But in fact, it has more to do with better thinking. Suppose we want to modify our function slightly, for example, to get the sum of all even squares of elements of the list.

+
[1,2,3,4] ▷ [1,4,9,16] ▷ [4,16] ▷ 20
+

Updating version 10 is extremely easy:

+ +

We just had to add another “transformation function”[^0216].

+
map (^2) [1,2,3,4] ⇔ [1,4,9,16]
+

The map function simply applies a function to all the elements of a list.

+

We didn’t have to modify anything inside the function definition. This makes the code more modular. But in addition you can think more mathematically about your function. You can also use your function interchangably with others, as needed. That is, you can compose, map, fold, filter using your new function.

+

Modifying version 1 is left as an exercise to the reader ☺.

+

If you believe we have 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 to grasp when first learning Haskell. A lot of effort tends to go into explaining the superiority of the functional approach. Then when you start using an imperative style with Haskell, it can be hard to understand when and how to use it.

+

But before talking about this Haskell super-power, we must talk about another essential aspect of Haskell: Types.

+ +

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 mark any difference between Name and AnotherType.
  • +
  • data Name = NameConstructor AnotherType does mark a difference.
  • +
  • data can construct structures which can be recursives.
  • +
  • deriving is magic and creates 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. Type inference makes it easy to detect where you used the wrong parameter at the wrong place, for example.

+

+Type inference +

+

Static typing is generally essential for fast execution. 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:

+ +

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 + iy).

+

Now compare with the amount of code necessary in C:

+ +

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, C++ templates:

+
#include <iostream>
+#include <complex>
+using namespace std;
+
+template<typename T>
+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<double>(5,3) )
+         << endl;
+    return 0;
+}
+

C++ does a far better job than C in this regard. But for more complex functions the syntax can be hard to follow: see this article for example.

+

In C++ you must declare that a function can work with different types. In Haskell, the opposite is the case. 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 run time. 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.

+ +

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:

+ +

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.

+ +

Now if you switch parameters of showInfos, the compiler complains! So this is a potential mistake you will never make again and the only price is to be more verbose.

+

Also notice that constructors are functions:

+ +

The syntax of data is mainly:

+ +

Generally the usage is to use the same name for the DataTypeName and DataTypeConstructor.

+

Example:

+ +

Also you can use the record syntax:

+ +

And many accessors are made for you. Furthermore you can use another order when setting values.

+

Example:

+ +

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:

+ +

If you really want to use an easier syntax you can use an infix name for constructors.

+ +

The number after infixr gives the precedence.

+

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.

+ +

When you add deriving (Show) to your data declaration, Haskell creates a show function for you. We’ll see soon how you can use your own show function.

+ + +

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.

+ +

We will also create a function which turns a list into an ordered binary tree.

+ +

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.
    • +
  • +
+ +

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) so we will be able to test equality and compare trees.

+ +

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:

+ +

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.

+ +

The treeFromList method remains identical.

+ +

And now, we can play:

+ +
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.

+ +
String binary tree:
+< "foo"
+: |--"bar"
+: |  `--"baz"
+: `--"gor"
+:    `--"yog"
+

As we can test equality and order trees, we can make tree of trees!

+ +
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 +
+ +

Which is equivalent to

+ +

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 said that Haskell is lazy.

+

In fact, if you are a bit pedantic, you should say that Haskell is non-strict. Laziness is just a common implementation for non-strict languages.

+

Then what does “not-strict” mean? 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:

+ +

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.

+ + +
+

Suppose we don’t mind having an ordered binary tree. Here is an infinite binary tree:

+ +

A complete binary tree where each node is equal to 0. Now I will prove you can manipulate this object using the following function:

+ +

See what occurs for this program:

+ +

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:

+ +

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:

+ +

Hint: I won’t talk more about this here. If you are interested in the generalization of map to other data structures, search for functor and fmap.

+

Our definition is now:

+ +

Look at the result for

+ +
<  0
+: |-- -1
+: |  |-- -2
+: |  |  |-- -3
+: |  |  `-- -1
+: |  `-- 0
+: |     |-- -1
+: |     `-- 1
+: `-- 1
+:    |-- 0
+:    |  |-- -1
+:    |  `-- 1
+:    `-- 2
+:       |-- 1
+:       `-- 3
+ +

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 are all 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

+
+ +

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 = ()
+

ou

+
value <- action2    -- where
+                    -- action2 :: 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 happens if the user enters 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! Our first improvement will simply 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. This is a very common type in Haskell.

+ +

What is this thing? Maybe is a type which takes one parameter. Its definition is:

+ +

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 read4, 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.

+ +

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].

+ +

We simply have to test the value in our main function.

+ +

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 just say here that error msg takes 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. So it’s clear just by looking at declared types which functions are pure and which are impure.

+

Why does purity matter? Among the many advantages, here are three:

+
    +
  • It is far easier to think about pure code than impure code.
  • +
  • Purity protects you from all the hard-to-reproduce bugs that are 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 iteration will be to prompt the user again and again until she enters a valid answer.

+

We keep the first part:

+ +

Now we create a function which will ask the user for an list of integers until the input is right.

+ +

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 really, if you just want to use IO just practice a little and remember to think about the type.

+

Finally our main function is much simpler:

+ +

We have finished with our introduction to IO. This was quite fast. Here are the main things to remember:

+
    +
  • in the do block, each expression must have the type IO a. You are then limited with regard to the range 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:

+ +

First remark: this looks imperative. 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, an imperative style is generally more appropriate.

+

But you should have noticed that 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. Whether a file exists or not is a difference in the possible states that the world can take.

+

In Haskell the current state of the world is not hidden. Rather, it is explicitly said that main is a function that potentially changes the state of the world. Its type is then something like:

+ +

Not all functions may access this variable. Those which have access to this variable are impure. Functions to which the world variable isn’t provided are pure5.

+

Haskell considers the state of the world as an input variable to main. But the real type of main is closer to this one6:

+ +

The () type is the unit type. Nothing to see here.

+

Now let’s rewrite our main function with this in mind:

+ +

First, we note that all functions which have side effects must have the type:

+ +

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’re working in a pure part of the 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.

+

This trick works like a charm. 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:

+ +

Before:

+ +

After:

+ +

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:

+ +

Even if for some lines the first x argument isn’t needed. The output type is a couple, (answer, newWorldValue). Each function f must have a type similar to:

+ +

Not only this, but we can also note that we always follow the same usage pattern:

+ +

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:

+ +

With, 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 symbols “disappear”. We will bind the two lines. Let’s define the bind function. Its type is quite intimidating at first:

+ +

But remember that (World -> (a,World)) is the type for an IO action. Now let’s rename it for clarity:

+ +

Some examples of functions:

+ +

getLine is an IO action which takes world as a parameter and returns a couple (String, World). This 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 that it changes the state of the world, but doesn’t yield any more data.

+

This new IO a type helps us simplify the type of bind:

+ +

It says that bind takes two IO actions as parameters and returns another IO action.

+

Now, remember the important patterns. The first was:

+ +

Look at the types:

+ +

Doesn’t it seem familiar?

+ +

The idea is to hide the World argument with this function. Let’s go: As an example imagine if we wanted to simulate:

+ +

Now, using the bind function:

+ +

As print is of type Show a => a -> (World -> ((), World)), we know res = () (unit type). If you didn’t see what was magic here, let’s try with three lines this time.

+ +

Which is equivalent to:

+ +

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

+ +

fr; Haskell a confectionné du sucre syntaxique pour vous : Ho Ho Ho! Merry Christmas Everyone! Haskell has made syntactical sugar for us:

+ +

Is replaced by:

+ +

Note that you can use x in action2 and x and y in action3.

+

But what about the lines not using the <-? Easy, another function blindBind:

+ +

I didn’t simplify this definition for the purposes of clarity. Of course, we can use a better notation: we’ll use the (>>) operator.

+

And

+ +

Is transformed into

+ +

Also, another function is quite useful.

+ +

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:

+ +

Is translated into:

+ +

You can compile this code to verify that it works.

+

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 think 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
  • +
+
+

In Haskell, Monad is a type class. To be an instance of this type class, you must provide the functions (>>=) and return. The function (>>) is derived from (>>=). Here is how the type class Monad is declared (basically):

+ +
+

Remarks:

+
    +
  • the keyword class is not your friend. A Haskell class is not a class of the kind you will find in object-oriented programming. A Haskell class has a lot of similarities with Java interfaces. A better word would have been typeclass, since 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 instances 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 your balance dipping below zero.

+ +

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

+ +

03_Hell/02_Monads/11_Monads.lhs

+
+

03_Hell/02_Monads/12_Monads.lhs

+

Not bad, but we can make it even better:

+ +

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 languages. 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 get this for free, thanks to laziness.

+
+

You could also replay these example with the definition of (>>=) for Maybe in mind:

+ +

The Maybe monad proved to be useful while being a very simple example. We saw the utility of the IO monad. But now for 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:

+ +

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 this syntactic sugar:

+ +

I won’t list all the monads, since there are many of them. Using monads simplifies the manipulation of several notions in pure languages. In particular, monads 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 monads7!

+

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.

+ +
+

Our first step is to create some pseudo-random number list:

+ +

Just as a reminder, here is the definition of treeFromList

+ +

and treeTakeDepth:

+ +

See the result of:

+ +
% 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

+ +

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.

+ + +
+

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.

+ +

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.

+ +

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.

+ +

Now run the program and be happy:

+ +

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.
  • +
+ +

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-strictness later.

  4. +
  5. For the brave, a more complete explanation of pattern matching can be found here.

  6. +
  7. Which is itself very similar to the javascript eval function, that is applied to a string containing JSON.

  8. +
  9. There are some unsafe exceptions to this rule. But you shouldn’t see such use in a real application except maybe for debugging purposes.

  10. +
  11. For the curious ones, the real type is data IO a = IO {unIO :: State# RealWorld -> (# State# RealWorld, a #)}. All the # has to do with optimisation and I swapped the fields in my example. But this is the basic idea.

  12. +
  13. 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.

  14. +
+
+
+
+ + + +
+
+ Published on 2012-02-08 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Helping-avoid-Haskell-Success/index.html b/src/Scratch/en/blog/Helping-avoid-Haskell-Success/index.html new file mode 100644 index 0000000..19a4756 --- /dev/null +++ b/src/Scratch/en/blog/Helping-avoid-Haskell-Success/index.html @@ -0,0 +1,292 @@ + + + + + YBlog - Tips in avoiding Haskell Success at all cost + + + + + + + + + + + + + + + + +
+ + +
+

Tips in avoiding Haskell Success at all cost

+ +
+
+
+
+
+Main image +
+
+

tl;dr: Few days ago there were about 20 job offer for Haskell. In only one day! How is that possible? As a real haskeller, I find this situation unbearable!

+

After all, we must avoid success at all cost. And I’ll help SPJ achieve this honorable goal.

+
+

Prevent Interest from beginner

+

Imagine a situation were you see people demonstrating some interest in learning Haskell.

+

Quick! Prevent them from going further.

+

If they come from the dynamic (uni-typed) languages like Python, Javascript…:

+
+

Haskell? A statically typed language??? Hmm… You mean like C and Java?

+
+

Such a remark should immediately shut down any interest in Haskell.

+

If they want to produce application with them:

+
+

Haskell? Isn’t it only a language for student! I don’t think it is useful for REAL WORLD applications!

+
+

If they just want to learn something new:

+
+

Haskell? Ah yes, I remember, mostly they only have the equivalent of Java interfaces and they stopped there. They don’t even have classes!!!! Can you imagine? I don’t even speak about class inheritance.

+

We’re in 2016! And they don’t even support basic Object Oriented Programming. What a joke!

+
+

If they love low level programming:

+
+

Haskell? Ah yes, I heard that lazyness make it impossible to think about code complexity and generally cause lot of space leaks.

+
+

And if it is not enough:

+
+

Haskell? Ah yes. I’m not fan of their Stop the World GC.

+
+

If they come from LISP and the statically typed language remark wasn’t enough. Try to mention the lack of macros in Haskell. Don’t mention template Haskell or even less Generics and all recent progress in GHC.

+

Make it difficult to install

+

Many hints there:

+
    +
  • Send them on another compiler than GHC
  • +
  • Explain that they should never use a binary distribution of GHC! And they must compile it manually! It might not stop them but it will make the installation process much more tedious.
  • +
  • Lie! Explain there is a severe security issue with latest tools. Explain they must use cabal-install 1.18 or older.
  • +
  • Also explain them that in order to be able to handle lib dependencies correctly they MUST first learn Nix! Never talk about stack, cabal freeze, … While Nix is great, forcing new user completely alien to all these concepts to first learn it before starting to write their first line of code can greatly reduce their enthusiasm. Bonus point if you make them believe you can only program in Haskell on NixOS.
  • +
+

Make it difficult to learn

+

Make new comers feel dumb

+

The very first thing to do is to explain how Haskell is so easy to learn. How natural it is for everybody you know. And except someone you always considered very dumb, everybody was very productive in Haskell in few hours.

+

Use vocabulary alien to them as much as possible. Here is a list of terms you should use in the very first minutes of your description of Haskell:

+
    +
  • catamorphism (bonus if you mention that the word come from the Greek κατα for catastrophe, that way you’ll look like a snob and you also use the word catastrophe in a Haskell context).
  • +
  • Monad! Of course you should use it ASAP! And explain they are exactly like potatoes or bananas shaped chairs. Double bonus if you explain that monad are really simple as they are just a monoid in the category of endofunctors.
  • +
  • GADTs
  • +
  • Yoneda Lemma
  • +
  • Homotopy Type Theory
  • +
  • +
+

Each of this term will hopefully be intimidating.

+

Tutorial authors

+

Please don’t provide an obvious first example like:

+ +

Instead prefer a fully servant example:

+ +

This nice example should overflow the number of new concepts a Haskell newcomer should deal with:

+
    +
  • Language extensions. Each extension can take a lot of time to be explained.
  • +
  • Strange notations: +
      +
    • :<|>
    • +
    • '[] instead of []
    • +
  • +
  • Proxy
  • +
  • Immediate usage of $
  • +
  • deriving ha ha! You’ll need to explain typeclasses first!
  • +
  • the definition for getItemById
  • +
+

Of course use the most of your energy explaining the language extensions first. Use a great deal of details and if possible use as much as possible references to Category Theory. You’ll get bonus points if you mention HoTT! Double bonus points if you explain that understanding all details in HoTT is essential to use Haskell on a daily basis.

+

Explain that what this does is incredible but for the wrong reasons. For example don’t mention why instance ToJSON Item is great. But insist that we achieved to serve a JSON with extreme elegance and simplicity. Keep insisting on the simplicity and forgot to mention type safety which is one of the main benefit of Servant.

+

If you’re afraid that this example might be too close to a real world product, you can simply use some advanced lenses examples:

+ +

Certainly a great example to start a new language with.

+

Library authors

+
    +
  1. Do your best not to respect versioning number policy to maximize the probability to break things.
  2. +
  3. Don’t write any documentation, type are enough!
  4. +
  5. Even better, add mistakes to your documentation
  6. +
  7. Each time you can use a meaningful notation, make it wrong. For example, if you have a symmetric relation use an asymmetric symbol to represent it.
  8. +
  9. If possible remove all function names and only use symbols of at least 5 letters: For example you can replace your function log :: Level -> String -> IO () by (<=.=$$.).
  10. +
+

If the the trends continue toward growth, then we might need to go further at the risk of breaking our own ecosystem:

+
    +
  • Split your libs as much as possible. The best would be to use one lib by symbol
  • +
  • Use unsafePerformIO as much as possible
  • +
  • Push to Hackage a version not accessible on your public repository
  • +
  • modify the package on Hackage using the same version but with incompatible API
  • +
  • Add memory leaks
  • +
  • Add bugs
  • +
  • Add back doors and publish how to use them
  • +
+

Yes we said, at all cost!

+

Conclusion & Mistake

+

So with all of this I believe we should be on the right track to avoid success at all cost!

+

Sorry? What?

+

Oh… Apparently I made a precedence mistake!

+

SPJ didn’t asked to avoid success $ at all cost but to avoid $ success at all cost1.

+

Sorry! My bad! Forget about all of this. Keep the good work everybody! Haskell is certainly one of the most awesome language in the world! Its community is also just great.

+

I’m really happy to see it growth every year. Thanks to all contributors making it possible to still have a lot of fun after many years using Haskell!

+

And the fact that in Haskell the right choice is preferred to the easiest choice, certainly helped.

+
+
+
    +
  1. A good point to use more LISP syntax.

  2. +
+
+
+
+ + + +
+
+ Published on 2016-10-01 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Higher-order-function-in-zsh/code/functional.sh b/src/Scratch/en/blog/Higher-order-function-in-zsh/code/functional.sh new file mode 100644 index 0000000..bf99fa3 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Higher-order-function-in-zsh/index.html b/src/Scratch/en/blog/Higher-order-function-in-zsh/index.html new file mode 100644 index 0000000..bd1c22c --- /dev/null +++ b/src/Scratch/en/blog/Higher-order-function-in-zsh/index.html @@ -0,0 +1,254 @@ + + + + + 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:

+ +

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 ⇒

+ +
    +
  • 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 ⇒

+ +

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 ⇒

+ +

After ⇒

+ +

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:

+ +
+
+ + + +
+
+ Published on 2011-09-28 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Holy-Haskell-Starter/index.html b/src/Scratch/en/blog/Holy-Haskell-Starter/index.html new file mode 100644 index 0000000..8ec709b --- /dev/null +++ b/src/Scratch/en/blog/Holy-Haskell-Starter/index.html @@ -0,0 +1,959 @@ + + + + + YBlog - Holy Haskell Project Starter + + + + + + + + + + + + + + + + +
+ + +
+

Holy Haskell Project Starter

+

A Haskell tutorial: from nothing to something useful

+ +
+
+
+
+
+Monty Python Holy Grail +
+
+

tl;dr: Learn how to start a new Haskell project. Translate a starter tool written in zsh in Haskell using its own result.

+
+

“Good Sir Knight, will you come with me to Camelot, and join us at the Round Table?”

+
+

In order to work properly with Haskell you need to initialize your environment. Typically, you need to use a cabal file, create some test for your code. Both, unit test and propositional testing (random and exhaustive up to a certain depth). You need to use git and generally hosting it on github. Also, it is recommended to use cabal sandboxes. And as bonus, an auto-update tool that recompile and retest on each file save.

+

In this article, we will create such an environment using a zsh script. Then we will write a Haskell project which does the same work as the zsh script. You will then see how to work in such an environment.

+

If you are starting to understand Haskell but consider yourself a beginner, this tutorial will show you how to make a real application using quite surprisingly a lot of features:

+
    +
  • use colorized output
  • +
  • interact with a user in command line
  • +
  • read/write files
  • +
  • kind of parse a file (in fact, simply split it)
  • +
  • use a templating system (mustache: fill a data structure, write files)
  • +
  • make a HTTP GET request then parse the JSON answer and use it
  • +
  • use random
  • +
  • create a cabal package
  • +
  • add and use non source files to a cabal package
  • +
  • Test your code (both unit testing and property testing)
  • +
+

zsh is by its nature more suitable to file manipulation. But the Haskell code is clearly more organized while quite terse for a multi-purpose language.

+

holy-project is on hackage. It can be installed with cabal update && cabal install holy-project.

+
+

I recently read this excellent article: How to Start a New Haskell Project.

+

While the article is very good, I lacked some minor informations1. Inspired by it, I created a simple script to initialize a new Haskell project. During the process I improved some things a bit.

+

If you want to use this script, the steps are:

+
    +
  1. Install Haskell
  2. +
  3. Make sure you have the latest cabal-install (at least 1.18)
  4. +
+ +
    +
  1. Download and run the script
  2. +
+ +

What does this script do that cabal init doesn’t do?

+
    +
  • Use cabal sandbox
  • +
  • It initialize git with the right .gitignore file.
  • +
  • Use tasty to organize your tests (HUnit, QuickCheck and SmallCheck).
  • +
  • Use -Wall for ghc compilation.
  • +
  • Will make references to Holy Grail
  • +
  • Search your default github username via github api.
  • +
+

zsh really?

+
+French insult +
+

Developing the script in zsh was easy. But considering its size, it is worth to rewrite it in Haskell. Furthermore, it will be a good exercise.

+

Patricide

+

In a first time, we initialize a new Haskell project with holy-haskell.sh:

+
+> ./holy-haskell.sh
+Bridgekeeper: Stop!
+Bridgekeeper: Who would cross the Bridge of Death
+Bridgekeeper: must answer me these questions three,
+Bridgekeeper: ere the other side he see.
+You: Ask me the questions, bridgekeeper, I am not afraid.
+
+Bridgekeeper: What is the name of your project?
+> Holy project
+Bridgekeeper: What is your name? (Yann Esposito (Yogsototh))
+>
+Bridgekeeper: What is your email? (Yann.Esposito@gmail.com)
+>
+Bridgekeeper: What is your github user name? (yogsototh)
+>
+Bridgekeeper: What is your project in less than ten words?
+> Start your Haskell project with cabal, git and tests.
+Initialize git
+Initialized empty Git repository in .../holy-project/.git/
+Create files
+    .gitignore
+    holy-project.cabal
+    Setup.hs
+    LICENSE (MIT)
+    test/Test.hs
+    test/HolyProject/Swallow/Test.hs
+    src/HolyProject/Swallow.hs
+    test/HolyProject/Coconut/Test.hs
+    src/HolyProject/Coconut.hs
+    src/HolyProject.hs
+    src/Main.hs
+Cabal sandboxing, install and test
+...
+  many compilations lines
+...
+Running 1 test suites...
+Test suite Tests: RUNNING...
+Test suite Tests: PASS
+Test suite logged to: dist/test/holy-project-0.1.0.0-Tests.log
+1 of 1 test suites (1 of 1 test cases) passed.
+All Tests
+  Swallow
+    swallow test:     OK
+  coconut
+    coconut:          OK
+    coconut property: OK
+      148 tests completed
+
+All 3 tests passed
+
+
+
+Bridgekeeper: What... is the air-speed velocity of an unladen swallow?
+You: What do you mean? An African or European swallow?
+Bridgekeeper: Huh? I... I don't know that.
+[the bridgekeeper is thrown over]
+Bridgekeeper: Auuuuuuuuuuuugh
+Sir Bedevere: How do you know so much about swallows?
+You: Well, you have to know these things when you're a king, you know.
+
+

The different steps are:

+
    +
  • small introduction quotes
  • +
  • ask five questions – three question sir…
  • +
  • create the directory for the project
  • +
  • init git
  • +
  • create files
  • +
  • sandbox cabal
  • +
  • cabal install and test
  • +
  • run the test directly in the terminal
  • +
  • small goodbye quotes
  • +
+

Features to note:

+
    +
  • color in the terminal
  • +
  • check some rules on the project name
  • +
  • random message if error
  • +
  • use ~/.gitconfig file in order to provide a default name and email.
  • +
  • use the github API which returns JSON to get the default github user name.
  • +
+

So, apparently nothing too difficult to achieve.

+

We should now have an initialized Haskell environment for us to work. The first thing you should do, is to go into this new directory and launch ‘./auto-update’ in some terminal. I personally use tmux on Linux or the splits in iTerm 2 on Mac OS X. Now, any modification of a source file will relaunch a compilation and a test.

+

The dialogs

+
+Bridge of Death +
+

To print the introduction text in zsh:

+ +

In the first Haskell version I don’t use colors. We see we can almost copy/paste. I just added the types.

+ +

Now let’s just add the colors using the ansi-terminal package. So we have to add ansi-terminal as a build dependency in our cabal file.

+

Edit holy-project.cabal to add it.

+
...
+build-depends:  base >=4.6 && <4.7
+                , ansi-terminal
+...
+

Now look at the modified Haskell code:

+ +

We could put this code in src/Main.hs. Declare a main function:

+ +

Make cabal install and run cabal run (or ./.cabal-sandbox/bin/holy-project). It works!

+

Five Questions – Three questions Sir!

+
+Bring out your dead! +
+

In order to ask questions, here is how we do it in shell script:

+ +

If we want to abstract things a bit, the easiest way in shell is to use a global variable2 which will get the value of the user input like this:

+ +

In Haskell we won’t need any global variable:

+ +

Now our main function might look like:

+ +

You could test it with cabal install and then ./.cabal-sandbox/bin/holy-project.

+

We will see later how to guess the answer using the .gitconfig file and the github API.

+

Using answers

+
+Castle of Aaaaarrrr???? +
+

Create the project name

+

I don’t really like the ability to use capital letter in a package name. So in shell I transform the project name like this:

+ +

In order to achieve the same result in Haskell (don’t forget to add the split package):

+ +

One important thing to note is that in zsh the transformation occurs on strings but in haskell we use list as intermediate representation:

+
zsh:
+"Holy grail" ==( ${project:gs/ /-/} )=> "Holy-grail"
+             ==( ${project:l}       )=> "holy-grail"
+
+haskell
+"Holy grail" ==( map toLower     )=> "holy grail"
+             ==( splitOneOf " -" )=> ["holy","grail"]
+             ==( intercalate "-" )=> "holy-grail"
+

Create the module name

+

The module name is a capitalized version of the project name where we remove dashes.

+ + +

The haskell version is made by hand where zsh already had a capitalize operation on string with many words. Here is the difference between the shell and haskell way (note I splitted the effect of concatMap as map and concat):

+
shell:
+"Holy-grail" ==( sed 's/-/ /g' )=> "Holy grail"
+             ==( ${(C)str}     )=> "Holy Grail"
+             ==( sed 's/ //g'  )=> "HolyGrail"
+
+haskell:
+"Holy-grail" ==( splitOneOf " -"    )=> ["Holy","grail"]
+             ==( map capitalizeWord )=> ["Holy","Grail"]
+             ==( concat             )=> "HolyGrail"
+

As the preceding example, in shell we work on strings while Haskell use temporary lists representations.

+

Check the project name

+

Also I want to be quite restrictive on the kind of project name we can give. This is why I added a check function.

+ +

Which verify the project name is not empty and use only letter, numbers and dashes:

+ +

Create the project

+
+Giant with three heads and mustaches +
+

Making a project will consists in creating files and directories whose name and content depends on the answer we had until now.

+

In shell, for each file to create, we used something like:

+ +

In Haskell, while possible, we shouldn’t put the file content in the source code. We have a relatively easy way to include external file in a cabal package. This is what we will be using.

+

Furthermore, we need a templating system to replace small part of the static file by computed values. For this task, I choose to use hastache, a Haskell implementation of Mustache templates3.

+

Add external files in a cabal project

+

Cabal provides a way to add files which are not source files to a package. You simply have to add a Data-Files: entry in the header of the cabal file:

+
data-files: scaffold/LICENSE
+            , scaffold/Setup.hs
+            , scaffold/auto-update
+            , scaffold/gitignore
+            , scaffold/interact
+            , scaffold/project.cabal
+            , scaffold/src/Main.hs
+            , scaffold/src/ModuleName.hs
+            , scaffold/src/ModuleName/Coconut.hs
+            , scaffold/src/ModuleName/Swallow.hs
+            , scaffold/test/ModuleName/Coconut/Test.hs
+            , scaffold/test/ModuleName/Swallow/Test.hs
+            , scaffold/test/Test.hs
+

Now we simply have to create our files at the specified path. Here is for example the first lines of the LICENSE file.

+
The MIT License (MIT)
+
+Copyright (c) {{year}} {{author}}
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+...
+

It will be up to our program to replace the {{year}} and {{author}} at runtime. We have to find the files. Cabal will create a module named Paths_holy_project. If we import this module we have the function genDataFileName at our disposal. Now we can read the files at runtime like this:

+ +

Create files and directories

+

A first remark is for portability purpose we shouldn’t use String for file path. For example on Windows / isn’t considered as a subdirectory character. To resolve this problem we will use FilePath:

+ +

Use Hastache

+

In order to use hastache we can either create a context manually or use generics to create a context from a record. This is the last option we will show here. So in a first time, we need to import some modules and declare a record containing all necessary informations to create our project.

+ + +

Once we have declared this, we should populate our Project record with the data provided by the user. So our main function should look like:

+ +

Finally we could use hastache this way:

+ +

We use external files in mustache format. We ask question to our user to fill a data structure. We use this data structure to create a context. Hastache use this context with the external files to create the project files.

+

Git and Cabal

+
+Tim +
+

We need to initialize git and cabal. For this we simply call external command with the system function.

+ +

Ameliorations

+

Our job is almost finished. Now, we only need to add some nice feature to make the application more enjoyable.

+

Better error message

+
+Rabbit +
+

The first one would be to add a better error message.

+ +

And also update where this can be called

+ +

Use .gitconfig

+

We want to retrieve the ~/.gitconfig file content and see if it contains a name and email information. We will need to access to the HOME environment variable. Also, as we use bytestring package for hastache, let’s take advantage of this library.

+ +

We could note I changed the ask function slightly to take a maybe parameter.

+ +

Concerning the parsing of .gitconfig, it is quite minimalist.

+ +

We could notice, getNameAndMail doesn’t read the full file and stop at the first occurrence of name and mail.

+

Use the github API

+
+Coconut and Swallow +
+

The task seems relatively easy, but we’ll see there will be some complexity hidden. Make a request on https://api.github.com/search/users?q=<email>. Parse the JSON and get the login field of the first item.

+

So the first problem to handle is to connect an URL. For this we will use the http-conduit package.

+

Generally, for simple request, we should use:

+ +

But, after some research, I discovered we must declare an User-Agent in the HTTP header to be accepted by the github API. So we have to change the HTTP Header, and our code became slightly more complex:

+ +

So now, we have a String containing a JSON representation. In javascript we would have used login=JSON.parse(body).items[0].login. How does Haskell will handle it (knowing the J in JSON is for Javascript)?

+

First we will need to add the lens-aeson package and use it that way:

+ +

It looks ugly, but it’s terse. In fact each function (^?), key and nth has some great mathematical properties and everything is type safe. Unfortunately I had to make my own jsonValueToString. I hope I simply missed a simpler existing function.

+

You can read this article on lens-aeson and prisms to know more.

+

Concurrency

+
+Priests +
+

We now have all the feature provided by the original zsh script shell. But here is a good occasion to use some Haskell great feature.

+

We will launch the API request sooner and in parallel to minimize our wait time:

+
import Control.Concurrent
+...
+main :: IO ()
+main = do
+    intro
+    gitconfig <- safeReadGitConfig
+    let (name,email) = getNameAndMail gitconfig
+    earlyhint <- newEmptyMVar
+    maybe   (putMVar earlyhint Nothing) -- if no email found put Nothing
+            (\hintmail -> do  -- in the other case request the github API
+                forkIO (putMVar earlyhint =<< getGHUser hintmail)
+                return ())
+            email
+    project <- ask "project name" Nothing
+    ioassert (checkProjectName project)
+             "Use only letters, numbers, spaces and dashes please"
+    let projectname = projectNameFromString project
+        modulename  = capitalize project
+    in_author       <- ask "name" name
+    in_email        <- ask "email" email
+    ghUserHint      <- if maybe "" id email /= in_email
+                            then getGHUser in_email
+                            else takeMVar earlyhint
+    in_ghaccount    <- ask "github account" ghUserHint
+    in_synopsis     <- ask "project in less than a dozen word?" Nothing
+    current_year    <- getCurrentYear
+    createProject $ Project projectname modulename in_author in_email
+                            in_ghaccount in_synopsis current_year
+    end
+

While it might feel a bit confusing, it is in fact quite simple.

+
    +
  1. declare an MVar. Mainly a variable which either is empty or contains something.
  2. +
  3. If we didn’t found any email hint, put Nothing in the MVar.
  4. +
  5. If we have an email hint, ask on the github API in a new process and once finished put the result in the MVar.
  6. +
  7. If the user enter an email different from the hint email, then just request the github api now.
  8. +
  9. If the user enter the same email, then wait for the MVar to be filled and ask the next question with the result.
  10. +
+

If you have a github account and had set correctly your .gitconfig, you might not even wait.

+

Project Structure

+

We have a working product. But, I don’t consider our job finished. The code is about 335 lines.

+

Considering that we:

+
    +
  • have 29 lines of import and 52 lines of comments (rest 255 lines)
  • +
  • ask questions
  • +
  • use a templating system to generate files
  • +
  • call an asynchronous HTTP request
  • +
  • parse JSON
  • +
  • parse .gitconfig
  • +
  • use colored output
  • +
+

This is quite few.

+

Modularizing

+
+The Black Knight +
+

For short programs it is not obvious to split them into different modules. But my personal preference is to split it anyway.

+

First we put all content of src/Main.hs in src/HolyProject.hs. We rename the main function by holyStarter. And our src/Main.hs should contains:

+ +

Of course you have to remember to rename the module of src/HolyProject.hs. I separated all functions in different submodules:

+
    +
  • HolyProject.GitConfig +
      +
    • getNameAndMailFromGitConfig: retrieve name an email from .gitconfig file
    • +
  • +
  • HolyProject.GithubAPI +
      +
    • searchGHUser: retrieve github user name using github API.
    • +
  • +
  • HolyProject.MontyPython +
      +
    • bk: bridge keeper speaks
    • +
    • you: you speak
    • +
    • ask: Ask a question and wait for an answer
    • +
  • +
  • HolyProject.StringUtils: String helper functions +
      +
    • projectNameFromString
    • +
    • capitalize
    • +
    • checkProjectName
    • +
  • +
+

The HolyProject.hs file contains mostly the code that ask questions, show errors and copy files using hastache.

+

One of the benefits in modularizing the code is that our main code is clearer. Some functions are declared only in a module and are not exported. This help us hide technical details. For example, the modification of the HTTP header to use the github API.

+

Documenting

+
+The Holy Grenade +
+

We didn’t take much advantage of the project structure yet. A first thing is to generate some documentation. Before most function I added comment starting with -- |. These comment will be used by haddock to create a documentation. First, you need to install haddock manually.

+ +

Be sure to have haddock in your PATH. You could for example add it like this:

+ +

And if you are at the root of your project you’ll get it. And now just launch:

+ +

And magically, you’ll have a documentation in dist/doc/html/holy-project/index.html.

+

Tests

+

While the Haskell static typing is quite efficient to prevent entire classes of bugs, Haskell doesn’t discard the need to test to minimize the number of bugs.

+

Unit Testing with HUnit

+
+A Witch! A Witch! +
+

It is generally said to test we should use unit testing for code in IO and QuickCheck or SmallCheck for pure code.

+

A unit test example on pure code is in the file test/HolyProject/Swallow/Test.hs:

+ +

Note swallow is (++). We group tests by group. Each group can contain some test suite. Here we have a test suite with only one test. The (@=?) verify the equality between its two parameters.

+

So now, we could safely delete the directory test/HolyProject/Swallow and the file src/HolyProject/Swallow.hs. And we are ready to make our own real world unit test. We will first test the module HolyProject.GithubAPI. Let’s create a file test/HolyProject/GithubAPI/Test.hs with the following content:

+ +

You have to modify your cabal file. More precisely, you have to add HolyProject.GithubAPI in the exposed modules of the library secion). You also have to update the test/Test.hs file to use GithubAPI instead of Swallow.

+

So we have our example of unit testing using IO. We search the github nickname for some people I know and we verify github continue to give the same answer as expected.

+

Property Testing with SmallCheck and QuickCheck

+
+My name is Zoot. Just Zoot +
+

When it comes to pure code, a very good method is to use QuickCheck and SmallCheck. SmallCheck will verify all cases up to some depth about some property. While QuickCheck will verify some random cases.

+

As this kind of verification of property is mostly doable on pure code, we will test the StringUtils module.

+

So don’t forget to declare HolyProject.StringUtils in the exposed modules in the library section of your cabal file. Remove all references to the Coconut module.

+

Modify the test/Test.hs to remove all references about Coconut. Create a test/HolyProject/StringUtils/Test.hs file containing:

+ +

The result is here:

+
+All Tests
+  StringUtils
+    SC projectNameFromString idempotent: OK
+      206 tests completed
+    SC capitalize idempotent:            OK
+      1237 tests completed
+    QC projectNameFromString idempotent: FAIL
+      *** Failed! Falsifiable (after 19 tests and 5 shrinks):
+      "a a"
+      Use --quickcheck-replay '18 913813783 2147483380' to reproduce.
+  GithubAPI
+    Yann:                                OK
+    Jasper:                              OK
+
+1 out of 5 tests failed
+
+

The test fail, but this is not an error. Our capitalize function shouldn’t be idempotent. I simply added this test to show what occurs when a test fail. If you want to look more closely to the error you could do this:

+ +

It is important to use ./interact instead of ghci. Because we need to tell ghci how to found the package installed.

+

Apparently, SmallCheck didn’t found any counter example. I don’t know how it generates Strings and using deeper search is really long.

+

Conclusion

+
+Rabbit +
+

Congratulation!

+

Now you could start programming in Haskell and publish your own cabal package.

+
+
+
    +
  1. For example, you have to install the test libraries manually to use cabal test.

  2. +
  3. There is no easy way to do something like name=$(ask name). Simply because $(ask name) run in another process which doesn’t get access to the standard input

  4. +
  5. Having a good level of power in templates is very difficult. imho Mustache has made the best compromise.

  6. +
+
+
+
+ + + +
+
+ Published on 2013-11-14 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Learn-Vim-Progressively/index.html b/src/Scratch/en/blog/Learn-Vim-Progressively/index.html new file mode 100644 index 0000000..7c042ce --- /dev/null +++ b/src/Scratch/en/blog/Learn-Vim-Progressively/index.html @@ -0,0 +1,384 @@ + + + + + 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.

+
+

If you liked this article, there is a follow up: Vim as IDE

+
+ +
+
+ + + +
+
+ Published on 2011-08-25 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Parsec-Presentation/index.html b/src/Scratch/en/blog/Parsec-Presentation/index.html new file mode 100644 index 0000000..3023c67 --- /dev/null +++ b/src/Scratch/en/blog/Parsec-Presentation/index.html @@ -0,0 +1,334 @@ + + + + + YBlog - Parsec Presentation + + + + + + + + + + + + + + + + +
+ + +
+

Parsec Presentation

+ +
+
+
+
+

AST
+

+
+

tl;dr: Short introduction to Parsec for beginner.

+
+
    +
  • The html presentation is here.
  • +
+
+() () () () () ( +) ( +

) () () () () () () () () ()

+
+ +
+
+

+Parsec +

+by Yann Esposito + + +
+
+
+

+Parsing +

+

+Latin pars (ōrātiōnis), meaning part (of speech). +

+
    +
  • +analysing a string of symbols +
  • +
  • +formal grammar. +
  • +
+
+
+

+Parsing in Programming Languages +

+

+Complexity: +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+Method + +Typical Example + +Output Data Structure +
+Splitting + +CSV + +Array, Map +
+Regexp + +email + +
    +
  • Fixed Layout Tree +
+Parser + +Programming language + +
    +
  • Most Data Structure +
+
+
+

+Parser & culture +

+

+In Haskell Parser are really easy to use. +

+

+Generally: +

+
    +
  • +In most languages: split then regexp then parse +
  • +
  • +In Haskell: split then parse +
  • +
+
+
+

+Parsing Example +

+

+From String: +

+
(1+3)*(1+5+9)
+

+To data structure: +

+

+AST
+

+
+
+

+Parsec +

+
+

+Parsec lets you construct parsers by combining high-order Combinators to create larger expressions. +

+

+Combinator parsers are written and used within the same programming language as the rest of the program. +

+

+The parsers are first-class citizens of the languages […]" +

+

+Haskell Wiki +

+
+
+
+

+Parser Libraries +

+

+In reality there are many choices: +

+ + + + + + + + + + + + + + + +
+attoparsec + +fast +
+Bytestring-lexing + +fast +
+Parsec 3 + +powerful, nice error reporting +
+
+
+

+Haskell Remarks (1) +

+

+spaces are meaningful +

+
f x   -- ⇔ f(x) in C-like languages
+f x y -- ⇔ f(x,y)
+
+
+

+Haskell Remarks (2) +

+

+Don’t mind strange operators (<*>, <$>).
Consider them like separators, typically commas.
They are just here to deal with types. +

+

+Informally: +

+
toto <$> x <*> y <*> z
+-- ⇔ toto x y z
+-- ⇔ toto(x,y,z) in C-like languages
+
+
+

+Minimal Parsec Examples +

+
whitespaces = many (oneOf "\t ")
+number = many1 digit
+symbol = oneOf "!#$%&|*+-/:<=>?@^_~"
+
+" \t " – whitespaces on " \t " "" – whitespaces on “32” “32” – number on “32”
+
+
+– number on " 
+          
+
+ + + +
+
+ Published on 2013-10-09 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Password-Management/index.html b/src/Scratch/en/blog/Password-Management/index.html new file mode 100644 index 0000000..66947b4 --- /dev/null +++ b/src/Scratch/en/blog/Password-Management/index.html @@ -0,0 +1,170 @@ + + + + + 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:

+ +

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.

+
+
+ + + +
+
+ Published on 2011-05-18 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Rational-Web-Framework-Choice/index.html b/src/Scratch/en/blog/Rational-Web-Framework-Choice/index.html new file mode 100644 index 0000000..a541851 --- /dev/null +++ b/src/Scratch/en/blog/Rational-Web-Framework-Choice/index.html @@ -0,0 +1,2296 @@ + + + + + YBlog - Rational Web Framework Choice + + + + + + + + + + + + + + + + +
+ + +
+

Rational Web Framework Choice

+ +
+
+
+
+
+Main image +
+
+

tl;dr: Determine with the most objectivity possible the best(s) web framework(s) depending on your needs. Here are the results. Please note the actual ability to take a rational decision is pretty bad for now.

+
+

This is it.
+You’ve got the next big idea.
+You just need to make a very simple web application.

+

It sounds easy! You just need to choose a good modern web framework, when suddenly:

+
+[Choice Paralysis][choice_paralysis] +
+Choice Paralysis +
+
+

After your brain stack overflowed, you decide to use a very simple methodology. Answer two questions:

+

Which language am I familiar with?
+What is the most popular web framework for this language?

+

Great! This is it.

+

But, you continually hear this little voice.

+
+

“You didn’t made a bad choice, yes. But …
+you hadn’t made the best either.”

+
+

This article try to determine in the most objective and rational way the best(s) web framework(s) depending on your needs. To reach this goal, I will provide a decision tool in the result section.

+

I will use the following methodology:

+

Methodology

+
    +
  1. Model how to make choice +
      +
    1. choose important parameters
    2. +
    3. organize (hierarchize) them
    4. +
    5. write down an objective chooser
    6. +
  2. +
  3. Grab objective quantified informations about web frameworks relatively to choosen parameters
  4. +
  5. Sanitize your data in order to handle imprecisions, lack of informations…
  6. +
  7. Apply the model of choice to your informations
  8. +
+
+

☞ Important Note
+I am far from happy to the actual result. There are a lot of biases, for example in the choice of the parameters. The same can be said about the data I gathered. I am using very imprecise informations. But, as far as I know, this is the only article which use many different parameters to help you choose a web framework.

+

This is why I made a very flexible decision tool:

+

Decision tool.

+
+

Model

+

Here are the important features (properties/parameters) I selected to make the choice:

+
    +
  1. Popularity, which correlate with: +
      +
    • number of tested libraries
    • +
    • facility to find learning material
    • +
    • ability to find another developer to work with
    • +
  2. +
  3. Efficiency, which is generally correlated to: +
      +
    • how much processing power you’ll need per user
    • +
    • maintenance price per user
    • +
    • how long the user will wait to see/update data
    • +
  4. +
  5. Expressiveness, which is generally correlated to: +
      +
    • faster development
    • +
    • flexibility, adaptability
    • +
  6. +
  7. Robustness, which correlate with: +
      +
    • security
    • +
    • fewer bugs
    • +
  8. +
+

Each feature is quite important and mostly independant from each other. I tried to embrace most important topics concerning web frameworks with these four properties. I am fully concious some people might lack another important feature. Nonetheless the methodology used here can be easily replicated. If you lack an important property add it at will and use this choice method.

+

Also each feature is very hard to measure with precision. This is why we will only focus on order of magnitude.

+

For each property a framework could have one of the six possible values: Excellent, Very Good, Good, Medium, Bad or Very Bad

+

So how to make a decision model from these informations?

+

One of the most versatile method is to give a weight for each cluster value. And to select the framework maximizing this score:

+
score(framework) = efficiency + robustness + expressiveness + popularity
+
+

For example:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Expressiveness1071-∞-∞-∞
Popularity554321
Efficiency1086421
Robustness1086421
+

Using this weighted table, that means:

+
    +
  • we discard the three least expressive clusters.
  • +
  • We don’t make any difference between excellent and very good in popularity.
  • +
  • Concerning efficient framework in excellent cluster will have 2 more points than the “very good” cluster.
  • +
+

So for each framework we compute its score relatively to a weighted table. And we select the best(s).

+

Example: Using this hypothetic framework and the preceeding table.

+ + + + + + + + + + + + + + + + + + + +
ExpressivenessPopularityEfficiencyRobustness
yogExcellentVery BadMediumVery Good
+
score(yog) = 10 + 0 + 4 + 8 = 22
+
+

Most needs should be expressed by such a weighted table. In the result section, we will discuss this further.

+

It is now time to try to get these measures.

+

Objective measures

+

None of the four properties I choosen can be measured with perfect precision. But we could get the order of magnitude for each.

+

I tried to focus on the framework only. But it is often easier to start by studying the language first.

+

For example, I have datas about popularity by language and I also have different datas concerning popularity by framework. Even if I use only the framework focused datas in my final decision model, it seemed important to me to discuss about the datas for the languages. The goal is to provide a tool to help decision not to give a decision for you.

+

Popularity

+

RedMonk Programming Language Rankings (January 2013) provide an apparent good measure of popularity. While not perfect the current measure feel mostly right. They create an image using stack overflow and github data. Vertical correspond to the number of questions on stackoverflow. Horizontal correspond to the number of projects on github.

+

If you look at the image, your eye can see about four clusters. The 1 cluster correspond to mainstream languages:

+
+Mainstream Languages Cluster from [RedMonk][redmonk] +
+Mainstream Languages Cluster from RedMonk +
+
+

Most developer know at least one of these language.

+

The second cluster is quite bigger. It seems to correspond to languages with a solid community behind them.

+
+Second tier languages from [RedMonk][redmonk] +
+Second tier languages from RedMonk +
+
+

I don’t get into detail, but you could also see third and fourth tier popular languages.

+

So:

+

Mainstream: JavaScript, Java, PHP, Python, Ruby, C#, C++, C, Objective-C, Perl, Shell

+

Good: Scala, Haskell, Visual Basic, Assembly, R, Matlab, ASP, ActionScript, Coffeescript, Groovy, Clojure, Lua, Prolog

+

Medium: Erlang, Go, Delphi, D, Racket, Scheme, ColdFusion, F#, FORTRAN, Arduino, Tcl, Ocaml

+

Bad: third tier Very Bad: fourth tier

+

I don’t thing I could find easily web frameworks for third or fourth tier languages.

+

For now, I only talked about language popularity. But what about framework popularity? I made a test using number of question on stackoverflow only. Then by dividing by two for each 6 cluster:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework#nb%
ExcellentRubyRails176208100%
Very GoodPythonDjango57385<50%
JavaServlet54139
JavaSpring31641
Node.jsnode.js27243
PHPCodeigniter21503
GroovyGrails20222
GoodRubySinatra8631<25%
PythonFlask7062
PHPLaravel6982
PHPKohana5959
Node.jsExpress5009
MediumPHPCake4554<13%
C♯ServiceStack3838
ScalaPlay3823
JavaWicket3819
DartDart3753
PHPSlim3361
PythonTornado3321
ScalaLift2844
GoGo2689
BadJavaTapestry1197<6%
C♯aspnet1000
HaskellYesod889
PHPSilex750
PHPLithium732
C♯nancy705
Very badJavaGrizzly622<3%
ErlangCowboy568
PerlDancer496
PHPSymphony2491
GoRevel459
ClojureCompojure391
PerlMojolicious376
ScalaScalatra349
ScalaFinagle336
PHPPhalcon299
jsRingo299
JavaGemini276
HaskellSnap263
PerlPlack257
ErlangElli230
JavaDropwizard188
PHPYaf146
JavaPlay1133
Node.jsHapi131
JavaVertx60
ScalaUnfiltered42
Conion18
Clojurehttp-kit17
PerlKelp16
PHPMicromvc13
LuaOpenresty8
C++cpoll-cppsp5
ClojureLuminus3
PHPPhreeze1
+

As we can see, our framework popularity indicator can be quite different from its language popularity. For now I didn’t found a nice way to merge the results from RedMonk with these one. So I’ll use these unperfect one. Hopefully the order of magninute is mostly correct for most framework.

+

Efficiency

+

Another objective measure is efficiency. We all know benchmarks are all flawed. But they are the only indicators concerning efficiency we have.

+

I used the benchmark from benchmarksgame. Mainly, there are five clusters:

+ + + + + + + + + + + + + + + + + + + + + + + +
1x→2xC, C++
2x→3xJava 7, Scala, OCamL, Haskell, Go, Common LISP
3x→10xC♯, Clojure, Racket, Dart
10x→30xErlang
30x→PHP, Python, Perl, Ruby, JRuby
+

Remarks concerning some very slow languages:

+
    +
  • PHP ; huge variations, can be about 1.5x C speed in best case.
  • +
  • Python ; huge variations, can be about 1.5x C speed in best case
  • +
  • Perl ; Can be about 3x C speed in best case
  • +
  • Ruby, JRuby ; mostly very slow.
  • +
+

This is a first approach. The speed of the language for basic benchmarks. But, here we are interrested in web programming. Fortunately techempower has made some tests focused on most web frameworks:

+

Web framework benchmarks.

+

These benchmark doesn’t fit well with our needs. The values are certainly quite imprecise to your real usage. The goal is just to get an order of magnitude for each framework. Another problem is the high number of informations.

+

As always, we should remember these informations are also imprecise. So I simply made some classes of efficiency.

+

Remark: I separated the clusters by using power of 2 relatively to the fastest.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework#nbslowness
ExcellentC++cpoll-cppsp114,711
Javgemini105,204
Luaopenresty93,882
Javservlet90,580
C++cpoll-pool89,167
Gogo76,024
Scafinagle68,413
Gorevel66,990
Javrest-express63,209
Very GoodJavwicket48,772>2×
Scascalatra48,594
Cljhttp-kit42,703
Javspring36,643>3×
PHPphp36,605
Javtapestry35,032
Cljcompojure32,088
JSringo31,962
Javdropwizard31,514
Cljluminus30,672
GoodScaplay-slick29,950>4×
Scaunfiltered29,782
Erlelli28,862
Javvertx28,075
JSnodejs27,598
Erlcowboy24,669
Conion23,649
Hklyesod23,304
JSexpress22,856>5×
Scaplay-scala22,372
Jav grizzly-jersey20,550
Pytornado20,372>6×
PHPphalcon18,481
Grvgrails18,467
Prlplack16,647>7×
PHPyaf14,388
MediumJShapi11,235>10×
Javplay19,979
Hklsnap9,196
Prlkelp8,250
Pyflask8,167
Javplay-java7,905
Jav play-java-jpa7,846
PHPmicromvc7,387
Prldancer5,040>20×
Prlmojolicious4,371
JSringo-conv4,249
Pydjango4,026
PHPcodeigniter3,809>30×
BadRbyrails3,445
Scalift3,311
PHPslim3,112
PHPkohana2,378>40×
PHPsilex2,364
Very BadPHPlaravel1,639>60×
PHPphreeze1,410
PHPlithium1,410
PHPfuel1,410
PHPcake1,287>80×
PHPsymfony2879>100×
C#aspnet-mvc871
Rbysinatra561>200×
C#servicestack51
Dardart0
C#nancy0
Prlweb-simple0
+

These are manually made clusters. But you get the idea. Certainly, some framework could jump between two different clusters. So this is something to remember. But as always, the order of magnitude is certainly mostly right.

+

Expressiveness

+

Now, how to objectively measure expressiveness?

+

RedMonk had a very good idea to find an objective (while imprecise) measure of each language expressiveness. Read this article for details.

+

After filtering languages suitable for web development, we end up with some clusters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguages
ExcellentCoffeescript, Clojure, Haskell
Very GoodRacket, Groovy, R, Scala, OCamL, F♯, Erlang, Lisp, Go
MediumPerl, Python, Objective-C, Scheme, Tcl, Ruby
BadLua, Fortran (free-format), PHP, Java, C++, C♯
Very BadAssembly, C, Javascript,
+

Unfortunately there is no information about dart. So I simply give a very fast look at the syntax. As it looked a lot like javascript and js is quite low. I decided to put it close to java.

+

Also an important remark, javascript score very badly here while coffeescript (compiling to js) score “excellent”. So if you intend to use a javascript framework but only with coffescript that should change substantially the score. As I don’t believe it is the standard. Javascript oriented framework score very badly regarding expressiveness.

+
+Click here to show/hide the table for frameworks +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework
ExcellentCljluminus
Cljhttp-kit
Cljcompojure
Hklsnap
Hklyesod
Very GoodErlelli
Erlcowboy
Gogo
Gorevel
Grvgrails
Scalift
Scafinagle
Scascalatra
Scaplay-scala
Scaplay-slick
Scaunfiltered
MediumPrlkelp
Prlplack
Prldancer
Prlweb-simple
Prlmojolicious
Pyflask
Pydjango
Pytornado
Rbyrails
Rbysinatra
BadC#nancy
C#aspnet-mvc
C#servicestack
C++cpoll-pool
C++cpoll-cppsp
Dardart
Javplay1
Javvertx
Javgemini
Javspring
Javwicket
Javservlet
Javtapestry
Javplay-java
Javdropwizard
Javrest-express
Javplay-java-jpa
Javgrizzly-jersey
Luaopenresty
PHPphp
PHPyaf
PHPcake
PHPfuel
PHPslim
PHPsilex
PHPkohana
PHPlaravel
PHPlithium
PHPphalcon
PHPphreeze
PHPmicromvc
PHPsymfony2
PHPcodeigniter
Very BadConion
JShapi
JSringo
JSnodejs
JSexpress
JSringo-conv
+
+

Robustness

+

I couldn’t find any complete study to give the number of bug relatively to each framework/language.

+

But one thing I saw from experience is the more powerful the type system the safest your application is. While the type system doesn’t remove completely the need to test your application a very good type system tend to remove complete classes of bug.

+

Typically, not using pointer help to reduce the number of bugs due to bad references. Also, using a garbage collector, reduce greatly the probability to access unallocated space.

+
+Static Type Properties from [James IRY Blog][typesanalysis] +
+Static Type Properties from James IRY Blog +
+
+

From my point of view, robustness is mostly identical to safety.

+

Here are the clusters:

+ + + + + + + + + + + + + + + + + + + +
ExcellentHaskell, Scheme, Erlang
Very GoodScala, Java, Clojure
GoodRuby, Python, Groovy, javascript, PHP
MediumC++, C#, Perl, Objective-C, Go, C
+

So applying this to frameworks gives the following clusters:

+
+Click here to show/hide the table for frameworks +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework
ExcellentErlelli
Erlcowboy
Hklsnap
Hklyesod
Very GoodCljluminus
Cljhttp-kit
Cljcompojure
Javplay1
Javvertx
Javgemini
Javspring
Javwicket
Javservlet
Javtapestry
Javplay-java
Javdropwizard
Javrest-express
Javplay-java-jpa
Javgrizzly-jersey
Scalift
Scafinagle
Scascalatra
Scaplay-scala
Scaplay-slick
Scaunfiltered
GoodGrvgrails
JShapi
JSringo
JSnodejs
JSexpress
JSringo-conv
Luaopenresty
PHPphp
PHPyaf
PHPcake
PHPfuel
PHPslim
PHPsilex
PHPkohana
PHPlaravel
PHPlithium
PHPphalcon
PHPphreeze
PHPmicromvc
PHPsymfony2
PHPcodeigniter
Pyflask
Pydjango
Pytornado
Rbyrails
Rbysinatra
MediumConion
C#nancy
C#aspnet-mvc
C#servicestack
C++cpoll-pool
C++cpoll-cppsp
Dardart
Gogo
Gorevel
Prlkelp
Prlplack
Prldancer
Prlweb-simple
Prlmojolicious
+
+

The result

+

For the result I initialized the table with my own needs.

+

And I am quite happy it confirms my current choice. I sware I didn’t given yesod any bonus point. I tried to be the most objective and factual as possible.

+

Now, it is up to you to enter your preferences.

+

On each line you could change how important a feature is for you. From essential to unsignificant. Of course you could change the matrix at will.

+

I just show a top 10 frameworks. In order to give a more understandable measure I provide the log of the score.

+ + + + + + + + + + + + + + + + + + + + + + + +
+ +Excellent + +Very good + +Good + +Medium + +Bad + +Very bad + +Importance +
+Expressiveness +
+Popularity +
+Efficiency +
+Robustness +
+
+Click to force refresh +
+
+ +
+ + +

I didn’t had the courage in explaining in what the scoring system is good. Mostly, if you use product instead of sum for the score you could use power of e for the values in the matrix. And you could see the matrix as a probability matrix (each line sum to 1). Which provide a slighly better intuition on whats going on.

+

Remember only that values are exponential. Do not double an already big value for example the effect would be extreme.

+

Conclusion

+

All of this is based as most as I could on objective data. The choice method seems both rather rational and classical. It is now up to you to edit the score matrix to set your needs.

+

I know that in the current state there are many flaws. But it is a first system to help make a choice rationally.

+

I encourage you to go further if you are not satisfied by my method.

+

The source code for the matrix shouldn’t be too hard to read. Just read the source of this webpage. You could change the positionning of some frameworks if you believe I made some mistake by placing them in some bad clusters.

+

So I hope this tool will help you in making your life easier.

+
+
+ + + +
+
+ Published on 2013-08-06 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/SVG-and-m4-fractals/code/yesodlogo.m4 b/src/Scratch/en/blog/SVG-and-m4-fractals/code/yesodlogo.m4 new file mode 100644 index 0000000..39ab18c --- /dev/null +++ b/src/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/src/Scratch/en/blog/SVG-and-m4-fractals/index.html b/src/Scratch/en/blog/SVG-and-m4-fractals/index.html new file mode 100644 index 0000000..86b33f1 --- /dev/null +++ b/src/Scratch/en/blog/SVG-and-m4-fractals/index.html @@ -0,0 +1,237 @@ + + + + + YBlog - Increase the power of deficient languages. + + + + + + + + + + + + + + + + +
+ + +
+

Increase the power of deficient languages.

+

Fractals with SVG and m4

+ +
+
+
+
+
+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:
  • +
+ +
    +
  • Printing something. Instead of print "Hello world!" here is the xslt equivalent:
  • +
+ +
    +
  • printing the value of a variable, instead of print myvar the xslt is:
  • +
+ +
    +
  • Just try to imagine how verbose it is to declare a function with this language.
  • +
+

The cure (m4 to the rescue)

+ +

Now just compile this file:

+ +

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:

+ +

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.

+
+
+ + + +
+
+ Published on 2011-10-20 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Safer-Haskell-Install/index.html b/src/Scratch/en/blog/Safer-Haskell-Install/index.html new file mode 100644 index 0000000..55a7170 --- /dev/null +++ b/src/Scratch/en/blog/Safer-Haskell-Install/index.html @@ -0,0 +1,151 @@ + + + + + YBlog - Safer Haskell Install + + + + + + + + + + + + + + + + +
+ + +
+

Safer Haskell Install

+ +
+
+
+
+
+to Haskell and Beyond!!! +
+
+

tl;dr: Install Haskell (OS X and Linux only) by pasting the following in your terminal:

+
curl https://raw.githubusercontent.com/yogsototh/install-haskell/master/install-haskell.sh | sudo zsh
+

update (28 march 2015): I now use Haskell LTS instead of a random stackage version.

+

If you are on windows, just download the Haskell Platform and follow the instruction to use Haskell LTS.

+

If you want to know the why and the how; you should read the entire article.

+
+

Why?

+

The main weakness of Haskell as nothing to do with the language itself but with its ecosystem1.

+

The main problem I’ll try to address is the one known as cabal hell. The community is really active in fixing the issue. I am very confident that in less than a year this problem will be one of the past. But to work today, I provide an install method that should reduce greatly two effects of cabal hell:

+
    +
  • dependency error
  • +
  • lost time in compilation (poor polar bears)
  • +
+

With my actual installation method, you should minimize your headache and almost never hit a dependency error. But there could exists some. If you encounter any dependency error, ask gently to the package manager to port its package to stackage.

+

So to install copy/paste the following three lines in your terminal:

+
curl https://raw.githubusercontent.com/yogsototh/install-haskell/master/install-haskell.sh | sudo zsh
+

How?

+

You can read the script and you will see that this is quite straightforward.

+
    +
  1. It downloads the latest GHC binary for you system and install it.
  2. +
  3. It does the same with the cabal program.
  4. +
  5. It updates your cabal config file to use Haskell LTS.
  6. +
  7. It enable profiling to libraries and executables.
  8. +
  9. It installs some useful binaries that might cause compilation error if not present.
  10. +
+

As the version of libraries is fixed up until you update the Haskell LTS version, you should never use cabal sandbox. That way, you will only compile each needed library once. The compiled objects/binaries will be in your ~/.cabal directory.

+

Some Last Words

+

This script use the latest Haskell LTS. So if you use this script at different dates, the Haskell LTS might have changed.

+

While it comes to cabal hell, some solutions are sandboxes and nix. Unfortunately, sandboxes didn’t worked good enough for me after some time. Furthermore, sandboxes forces you to re-compile everything by project. If you have three yesod projects for example it means a lot of time and CPU. Also, nix didn’t worked as expected on OS X. So fixing the list of package to a stable list of them seems to me the best pragmatic way to handle the problem today.

+

From my point of view, Haskell LTS is the best step in the right direction. The actual cabal hell problem is more a human problem than a tool problem. This is a bias in most programmer to prefer resolve social issues using tools. There is nothing wrong with hackage and cabal. But for a package manager to work in a static typing language as Haskell, packages must work all together. This is a great strength of static typed languages that they ensure that a big part of the API between packages are compatible. But this make the job of package managing far more difficult than in dynamic languages.

+

People tend not to respect the rules in package numbering2. They break their API all the time. So we need a way to organize all of that. And this is precisely what Haskell LTS provide. A set of stable packages working all together. So if a developer break its API, it won’t work anymore in stackage. And whether the developer fix its package or all other packages upgrade their usage. During this time, Haskell LTS end-users will be able to develop without dependency issues.

+
+

+The image of the cat about to jump that I slightly edited can found here +

+
+
+
    +
  1. By ecosystem of a language I mean, the community, the tools, the documentations, the deployment environments, the businesses using the language, etc… Mainly everything that has nothing to do with the detail of a programming language but has to do on how and why we use it.

  2. +
  3. I myself am guilty of such behavior. It was a beginner error.

  4. +
+
+
+
+ + + +
+
+ Published on 2014-08-16 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Social-link-the-right-way/index.html b/src/Scratch/en/blog/Social-link-the-right-way/index.html new file mode 100644 index 0000000..5358b7d --- /dev/null +++ b/src/Scratch/en/blog/Social-link-the-right-way/index.html @@ -0,0 +1,300 @@ + + + + + YBlog - Social link the right way + + + + + + + + + + + + + + + + +
+ + +
+

Social link the right way

+ +
+
+
+
+
+Main image +
+ +

The problem

+

Ever been on a website and want to tweet about it? Fortunately, the website might have a button to help you. But do you really know what this button do?

+

The “Like”, “Tweet” and “+1” buttons will call a javascript. It will get access to your cookies. It helps the provider of the button to know who you are.

+

In plain English, the “+1” button will inform Google you are visiting the website, even if you don’t click on “+1”. The same is true for the “like” button for facebook and the “tweet this” button for twitter.

+

The problem is not only a privacy issue. In fact (sadly imho) this isn’t an issue for most people. These button consume computer ressources. Far more than a simple link. It thus slow down a bit the computer and consume energy. These button could also slow down the rendering of your web page.

+

Another aspect is their design. Their look and feel is mostly imposed by the provider.

+

The most problematic aspect in my opinion is to use a third party js on your website. What if tomorrow twitter update their tweet button? If the upgrade break something for only a minority of people, they won’t fix it. This could occur anytime without any notification. They just have to add a document.write in their js you call asynchronously and BAM! Your website is just an empty blank page. And as you call many external ressources, it can be very difficult to find the origin of the problem.

+

Using social network buttons:

+
    +
  • Pros: +
      +
    • help user share your website,
    • +
    • can provide a popularity indicator to your users.
    • +
  • +
  • Cons: +
      +
    • you help tracking your users,
    • +
    • generally doesn’t follow the design of your website,
    • +
    • use more computer ressources,
    • +
    • slow down your website,
    • +
    • executing third party js can break things silently.
    • +
  • +
+

Solutions

+

I will provide you two solutions with the following properties:

+
    +
  • Pros: +
      +
    • help user share your website,
    • +
    • doesn’t follow your user,
    • +
    • use almost no computer ressource,
    • +
    • doesn’t slow down your website,
    • +
    • doesn’t execute any third party js on your website.
    • +
  • +
  • Cons: +
      +
    • doesn’t provide any popularity information.
    • +
  • +
+

Solution 1 (no js):

+ +

But you have to replace $url$ by the current url.

+

Solution 2 (Just copy/paste):

+

If you don’t want to write the url yourself, you could use some minimal js:

+ +

Here is the result:

+
+ + +
+

Good looking solutions

+

If you don’t want just text but nice icons. You have many choices:

+
    +
  • Use images <img src="..."/> in the links.
  • +
  • Use icon fonts
  • +
+

As the first solution is pretty straightforward, I’ll explain the second one.

+
    +
  1. Download the icon font here
  2. +
  3. put the font file(s) at some place (here ‘fonts/social_font.ttf’ relatively to your css file)
  4. +
  5. Add this to your css
  6. +
+ +

Now add this to your html:

+

Solution 1 (without js):

+ +

Solution 2 (same with a bit more js):

+ +

Here is the result:

+
+
+

· ·

+
+ +
+

Conclusion

+
    +
  1. You get your design back,
  2. +
  3. You stop to help tracking people,
  4. +
  5. You use less computer ressources and more generally power ressources which is good for the planet,
  6. +
  7. Your web pages will load faster.
  8. +
+

ps: On my personal website I continue to use Google analytics. Therefore, Google (and only Google, not facebook nor twitter) can track you here. But I might change this in the future.

+
+
+ + + +
+
+ Published on 2013-03-14 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Typography-and-the-Web/index.html b/src/Scratch/en/blog/Typography-and-the-Web/index.html new file mode 100644 index 0000000..5341036 --- /dev/null +++ b/src/Scratch/en/blog/Typography-and-the-Web/index.html @@ -0,0 +1,165 @@ + + + + + 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:

+ +

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. +
+
+
+
+ + + +
+
+ Published on 2012-02-02 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Vim-as-IDE/index.html b/src/Scratch/en/blog/Vim-as-IDE/index.html new file mode 100644 index 0000000..7710650 --- /dev/null +++ b/src/Scratch/en/blog/Vim-as-IDE/index.html @@ -0,0 +1,333 @@ + + + + + YBlog - Vim as IDE + + + + + + + + + + + + + + + + +
+ + +
+

Vim as IDE

+ +
+
+
+
+
+Main image +
+
+

tl;dr: How to use vim as a very efficient IDE

+

In Learn Vim Progressively I’ve show how Vim is great for editing text, and navigating in the same file (buffer). In this short article you’ll see how I use Vim as an IDE. Mainly by using some great plugins.

+
+

Vim Plugin Manager

+

There are a lot of Vim plugins. To manage them I use vim-plug.

+

To install it:

+ +
+

☞ Note I have two parts in my .vimrc. The first part contains the list of all my plugins. The second part contains the personal preferences I setted for each plugin. I’ll separate each part by ... in the code.

+
+

Survival

+

Colorscheme

+
+Solarized theme +
+

Before anything, you should protect your eyes using a readable and low contrast colorscheme.

+

For this I use solarized dark. To add it, you only have to write this in your ~/.vimrc file:

+
call plug#begin('~/.vim/plugged')
+
+Plug 'altercation/vim-colors-solarized'
+
+call plug#end()
+
+" -- solarized personal conf
+set background=dark
+try
+    colorscheme solarized
+catch
+endtry
+

Minimal hygiene

+

You should be able to see and destroy trailing whitespaces.

+
+Trim whitespaces +
+
Plug 'bronson/vim-trailing-whitespace'
+

You can clean trailing whitespace with :FixWhitespace.

+

And also you should see your 80th column.

+
if (exists('+colorcolumn'))
+    set colorcolumn=80
+    highlight ColorColumn ctermbg=9
+endif
+
+80th column +
+

File Management

+

One of the most important hidden skills in programming is the ability to search and find files in your projects.

+

The majority of people use something like NERDTree. This is the classical left column with a tree of files of your project. I stopped to use this. And you should probably too.

+

I switched to unite. No left column lost. Faster to find files. Mainly it works like Spotlight on OS X.

+

First install ag (the silver search). If you don’t know ack or ag your life is going to be upgraded. This is a simple but essential tool. It is mostly a grep on steroids.

+
" Unite
+"   depend on vimproc
+"   ------------- VERY IMPORTANT ------------
+"   you have to go to .vim/plugin/vimproc.vim and do a ./make
+"   -----------------------------------------
+Plug 'Shougo/vimproc.vim'
+Plug 'Shougo/unite.vim'
+
+...
+
+let g:unite_source_history_yank_enable = 1
+try
+  let g:unite_source_rec_async_command='ag --nocolor --nogroup -g ""'
+  call unite#filters#matcher_default#use(['matcher_fuzzy'])
+catch
+endtry
+" search a file in the filetree
+nnoremap <space><space> :split<cr> :<C-u>Unite -start-insert file_rec/async<cr>
+" reset not it is <C-l> normally
+:nnoremap <space>r <Plug>(unite_restart)
+

Now type space twice. A list of files appears. Start to type some letters of the file you are searching for. Select it, type return and bingo the file opens in a new horizontal split.

+
+Unite example +
+

If something goes wrong just type <space>r to reset the unite cache.

+

Now you are able to search file by name easily and efficiently.

+

Now search text in many files. For this you use ag:

+
Plug 'rking/ag.vim'
+...
+" --- type ° to search the word in all files in the current dir
+nmap ° :Ag <c-r>=expand("<cword>")<cr><cr>
+nnoremap <space>/ :Ag
+

Don’t forget to add a space after the :Ag.

+

These are two of the most powerful shortcut for working in a project. using ° which is nicely positioned on my azerty keyboard. You should use a key close to *.

+

So what ° is doing? It reads the string under the cursor and search for it in all files. Really useful to search where a function is used.

+

If you type <space>/ followed by a string, it will search for all occurrences of this string in the project files.

+

So with this you should already be able to navigate between files very easily.

+

Language Agnostic Plugins

+

Git

+
+Show modified lines +
+

Show which line changed since your last commit.

+
Plug 'airblade/vim-gitgutter'
+

And the “defacto” git plugin:

+
Plug 'tpope/vim-fugitive'
+

You can reset your changes from the latest git commit with :Gread. You can stage your changes with :Gwrite.

+
+Reset changes +
+

Align things

+
Plug 'junegunn/vim-easy-align'
+
+...
+
+" Easy align interactive
+vnoremap <silent> <Enter> :EasyAlign<cr>
+

Just select and type Return then space. Type Return many type to change the alignments.

+

If you want to align the second column, Return then 2 then space.

+
+Easy align example +
+

Basic auto completion: C-n & C-p

+

Vim has a basic auto completion system. The shortcuts are C-n and C-p while you are in insert mode. This is generally good enough in most cases. For example when I open a file not in my configured languages.

+

Haskell

+

My current Haskell programming environment is great!

+

Each time I save a file, I get a comment pointing to my errors or proposing me how to improve my code.

+

So here we go:

+
+

☞ Don’t forget to install ghc-mod with: cabal install ghc-mod

+
+
" ---------- VERY IMPORTANT -----------
+" Don't forget to install ghc-mod with:
+" cabal install ghc-mod
+" -------------------------------------
+
+Plug 'scrooloose/syntastic'             " syntax checker
+" --- Haskell
+Plug 'yogsototh/haskell-vim'            " syntax indentation / highlight
+Plug 'enomsg/vim-haskellConcealPlus'    " unicode for haskell operators
+Plug 'eagletmt/ghcmod-vim'
+Plug 'eagletmt/neco-ghc'
+Plug 'Twinside/vim-hoogle'
+Plug 'pbrisbin/html-template-syntax'    " Yesod templates
+
+...
+
+" -------------------
+"       Haskell
+" -------------------
+let mapleader="-"
+let g:mapleader="-"
+set tm=2000
+nmap <silent> <leader>ht :GhcModType<CR>
+nmap <silent> <leader>hh :GhcModTypeClear<CR>
+nmap <silent> <leader>hT :GhcModTypeInsert<CR>
+nmap <silent> <leader>hc :SyntasticCheck ghc_mod<CR>:lopen<CR>
+let g:syntastic_mode_map={'mode': 'active', 'passive_filetypes': ['haskell']}
+let g:syntastic_always_populate_loc_list = 1
+nmap <silent> <leader>hl :SyntasticCheck hlint<CR>:lopen<CR>
+
+" Auto-checking on writing
+autocmd BufWritePost *.hs,*.lhs GhcModCheckAndLintAsync
+
+"  neocomplcache (advanced completion)
+autocmd BufEnter *.hs,*.lhs let g:neocomplcache_enable_at_startup = 1
+function! SetToCabalBuild()
+    if glob("*.cabal") != ''
+        set makeprg=cabal\ build
+    endif
+endfunction
+autocmd BufEnter *.hs,*.lhs :call SetToCabalBuild()
+
+" -- neco-ghc
+let $PATH=$PATH.':'.expand("~/.cabal/bin")
+

Just enjoy!

+
+hlint on save +
+

I use - for my leader because I use , a lot for its native usage.

+
    +
  • -ht will highlight and show the type of the block under the cursor.
  • +
  • -hT will insert the type of the current block.
  • +
  • -hh will unhighlight the selection.
  • +
+
+Auto typing on save +
+

Clojure

+
+Rainbow parenthesis +
+

My main language at work is Clojure. And my current vim environment is quite good. I lack the automatic integration to lein-kibit thought. If I have the courage I might do it myself one day. But due to the very long startup time of clojure, I doubt I’ll be able to make a useful vim plugin.

+

So mainly you’ll have real rainbow-parenthesis (the default values are broken for solarized).

+

I used the vim paredit plugin before. But it is too restrictive. Now I use sexp which feel more coherent with the spirit of vim.

+
" " -- Clojure
+Plug 'kien/rainbow_parentheses.vim'
+Plug 'guns/vim-clojure-static'
+Plug 'guns/vim-sexp'
+Plug 'tpope/vim-repeat'
+Plug 'tpope/vim-fireplace'
+
+...
+
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesActivate
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadRound
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadSquare
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadBraces
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl setlocal iskeyword+=?,-,*,!,+,/,=,<,>,.,:
+" -- Rainbow parenthesis options
+let g:rbpt_colorpairs = [
+	\ ['darkyellow',  'RoyalBlue3'],
+	\ ['darkgreen',   'SeaGreen3'],
+	\ ['darkcyan',    'DarkOrchid3'],
+	\ ['Darkblue',    'firebrick3'],
+	\ ['DarkMagenta', 'RoyalBlue3'],
+	\ ['darkred',     'SeaGreen3'],
+	\ ['darkyellow',  'DarkOrchid3'],
+	\ ['darkgreen',   'firebrick3'],
+	\ ['darkcyan',    'RoyalBlue3'],
+	\ ['Darkblue',    'SeaGreen3'],
+	\ ['DarkMagenta', 'DarkOrchid3'],
+	\ ['Darkblue',    'firebrick3'],
+	\ ['darkcyan',    'SeaGreen3'],
+	\ ['darkgreen',   'RoyalBlue3'],
+	\ ['darkyellow',  'DarkOrchid3'],
+	\ ['darkred',     'firebrick3'],
+	\ ]
+

Working with Clojure will becomre quite smoother. You can eval any part of your code, you must launch a Clojure REPL manually in another terminal thought.

+

Last words

+

I hope it will be useful.

+

Last but not least, if you want to use my vim configuration you can get it here:

+

github.com/yogsototh/vimrc

+
+
+ + + +
+
+ Published on 2014-12-07 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Yesod-excellent-ideas/index.html b/src/Scratch/en/blog/Yesod-excellent-ideas/index.html new file mode 100644 index 0000000..84a00f9 --- /dev/null +++ b/src/Scratch/en/blog/Yesod-excellent-ideas/index.html @@ -0,0 +1,177 @@ + + + + + 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:

+ +

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:

+ +

As AnotherPageR is of type URL and it could not contains something nefarious. It will be an URL safe. Not something like:

+ +

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:

+ +

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:

+ +

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.

+ +

is invalid by default (you can make it valid, but I don’t think it is a good idea).

+

You’d better

+ +

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!

+
+
+ + + +
+
+ Published on 2011-10-04 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/Yesod-tutorial-for-newbies/code/Echo.hs b/src/Scratch/en/blog/Yesod-tutorial-for-newbies/code/Echo.hs new file mode 100644 index 0000000..4ba6b60 --- /dev/null +++ b/src/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/src/Scratch/en/blog/Yesod-tutorial-for-newbies/code/Mirror.hs b/src/Scratch/en/blog/Yesod-tutorial-for-newbies/code/Mirror.hs new file mode 100644 index 0000000..c7a006a --- /dev/null +++ b/src/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/src/Scratch/en/blog/Yesod-tutorial-for-newbies/code/article.hamlet b/src/Scratch/en/blog/Yesod-tutorial-for-newbies/code/article.hamlet new file mode 100644 index 0000000..6c32d42 --- /dev/null +++ b/src/Scratch/en/blog/Yesod-tutorial-for-newbies/code/article.hamlet @@ -0,0 +1,2 @@ +

#{articleTitle article} +
#{articleContent article} diff --git a/src/Scratch/en/blog/Yesod-tutorial-for-newbies/code/articles.hamlet b/src/Scratch/en/blog/Yesod-tutorial-for-newbies/code/articles.hamlet new file mode 100644 index 0000000..9ef45ac --- /dev/null +++ b/src/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 +

+
+

tl;dr: Some hints on how to make great documentation for Haskell libraries.

+
    +
  1. Create a Tutorial module containing nothing except documentation.
  2. +
  3. Mention the Tutorial module in your cabal description
  4. +
  5. Use doctest to check your documentation is up to date
  6. +
  7. For more complex real world examples, link to the source of some test.
  8. +
+
+

Great documentation make a big difference. A bad documentation could simply make people not using your lib.

+

My friend was learning Haskell. To start he tried a Haskell library to make a small application. The documentation was deprecated to the point he wasn’t able to make a basic example work. How do you believe he felt? What does he thought about Haskell in general?

+

So here are my hint on how to make a great documentation in Haskell.

+

Documentation can take many different form.

+
    +
  1. Tutorials/Guides – write some prose which friendly take a user by hand and help him
  2. +
  3. Examples – how to use each function
  4. +
  5. Generated API Documentation – haddock
  6. +
+

Hints

+

Tutorials/Guides

+
    +
  1. Create a new module named Tutorial (or Guide.GuideTopic)
  2. +
  3. Create a link to the tutorial in the cabal description
  4. +
  5. Create a link to the tutorial in your README
  6. +
  7. Here is an example some Tutorial module content:
  8. +
+ +

To prevent obsolescence of your tutorial, use doctest.

+

That way when you’ll do a stack test or cabal test you’ll get errors if some example doesn’t work anymore.

+

Examples (doctest)

+

doctest is a great way to provide examples in your code documentation. These example will then be used as tests. Apparently it comes from Python community.

+

To use doctest, this is very simple:

+ +

And to make it works simply verify you have a test bloc in your .cabal file looking like this:

+
test-suite doctest
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test
+  main-is: DocTest.hs
+  build-depend: base >= 4.7 && < 5
+              , <YOUR_LIBRARY> 
+              , Glob >= 0.7
+              , doctest >= 0.9.12
+

and in test/DocTest.hs simply use

+ +

Now stack test or cabal test will check the validity of your documentation.

+

Bonuses

+

Verifying documentation coverage

+
    +
  1. Install haddock stack install haddock or cabal install haddock
  2. +
  3. Launch haddock without output format:
  4. +
+
> haddock src/**/*.hs
+Haddock coverage:
+ 100% ( 15 / 15) in 'Data.Duration'
+ 100% (  3 /  3) in 'Data.Duration.Tutorial'
+

Continuous Integration

+

There are plenty of alternative solution. I provide the one I believe would be used by most people. So if you use github simply create an account on travis.

+

Add a .travis.yml file in your repo containing the content of the file here and remove the builds you don’t need. It will build your project using a lot of different GHC versions and environemnts.

+

If you are afraid by such its complexity you might just want to use this one:

+ +

Don’t forget to activate your repo in travis.

+

For some bonus points add the build status badge in your README.md file:

+ +

Congratulation! Now if you break your documentation examples, you’ll get notified.

+

Badges

+

You could add badges to your README.md file.

+

Here is a list of some: shields.io

+

Hackage

+ +

Stackage

+

If you didn’t declared your package to stackage, please do it. It isn’t much work. Just edit a file to add your package. And you’ll could be able to add another badge:

+ +

See Stackage Badges for more informations.

+

Creating a new project with stack

+

If you use stack I suggest you to use the tasty-travis template. It will include the boilerplate for:

+ +

So edit your ~/.stack/config.yaml like this:

+
templates:
+  params:
+      author-name: Your Name
+      author-email: your@mail.com
+      copyright: 'Copyright: (c) 2016 Your Name'
+      github-username: yourusername
+      category: Development
+

And then you can create a new projec with:

+
stack new my-project tasty-travis
+

Generated Documentation

+

Even not doing anything, if you submit your library to hackage, haddock should generate some API documentation for free.

+

But to make real documentation you need to add some manual annotations.

+

Functions:

+
-- | My function description
+myFunction :: T1 -- ^ arg1 description
+           -> T2 -- ^ arg2 description
+myFunction arg1 arg2 = ...
+

Data:

+
data MyData a b
+  = C1 a b -- ^ doc for constructor C1
+  | C2 a b -- ^ doc for constructor C2
+
+data MyData a b
+  = C { a :: TypeA -- ^ field a description
+      , b :: TypeB -- ^ field b description
+      }
+

Module:

+
{-|
+Module    : MyModule
+Description: Short description
+Copyright : (c)
+License : MIT
+
+Here is a longer description of this module.
+With some code symbol @MyType@.
+And also a block of code:
+
+@
+data MyData = C Int Int
+
+myFunction :: MyData -> Int
+@
+
+-}
+

Documentation Structure:

+
module MyModule (
+  -- * Classes
+  C(..),
+  -- * Types
+  -- ** A data type
+  T,
+  -- ** A record
+  R,
+  -- * Some functions
+  f, g
+  ) where
+

That will generate headings.

+

Other Random Ideas

+

In Haskell we have great tools like hayoo! and hoogle.

+

And hackage and stackage provide also a lot of informations.

+

But generally we lack a lot of Tutorials and Guides. This post was an attempt to help people making more of them.

+

But there are other good ideas to help improve the situation.

+ +

In clojure when you create a new project using lein new my-project a directory doc is created for you. It contains a file with a link to this blog post:

+ +

Having a page by function/symbol with comments

+

If you try to search for some clojure function on a search engine there is a big chance the first result will link to:

+ +

For each symbol necessiting a documentation. You don’t only have the details and standard documentation. You’ll also get:

+ +

clojuredocs.org is an independant website from the official Clojure website.

+

Most of the time, if you google the function you search you end up on clojuredocs for wich there are many contributions.

+

Currently stackage is closer to these feature than hackage. Because on stackage you have access to the README and also some comments by package.

+

I believe it would be more efficient to have at least a page by module and why not a page by symbol (data, functions, typeclasses…).

+

For example, we could provide details about foldl for example. Also as there would be less information to display, it will make the design cleaner.

+

Today, if you want to help documenting, you need to make a PR to the source of some library. While if we had an equivalent to clojuredocs for Haskell, adding documentation would simply be a few clicks away:

+
    +
  1. login
  2. +
  3. add/edit some example, comments, see-also section
  4. +
+

There are more than 23k people on /r/haskell. If only 1% of them would take 10 minutes adding a bit of documentation it will certainly change a lot of things in the percieved documentation quality.

+

And last but not least,

+

Design is important

+
+Design is Important +
+

Design is a vague word. A good design should care not only about how something look, but also how users will interact with it. For example by removing things to focus on the essential.

+

When I stumble upon some random blog post or random specification in the Haskell community, I had too much a feeling of old fashioned design.

+

If you look at node.js community lot of their web page look cleaner, easier to read and in the end, more user friendly.

+

Haskell is very different from node, I wouldn’t like to replace all long and precise documentation with short human unprecise concepts. I don’t want to transform scientific papers by tweets.

+

But like the scientific community has upgraded with the use of LaTeX, I believe we could find something similar that would make, very clean environment for most of us. A kind of look and feel that will be

+]]> + + + Vim as IDE + + http://yannesposito.com/Scratch/en/blog/Vim-as-IDE/index.html + 2014-12-07T00:00:00Z + 2014-12-07T00:00:00Z + +Main image + +
+

tl;dr: How to use vim as a very efficient IDE

+

In Learn Vim Progressively I’ve show how Vim is great for editing text, and navigating in the same file (buffer). In this short article you’ll see how I use Vim as an IDE. Mainly by using some great plugins.

+
+

Vim Plugin Manager

+

There are a lot of Vim plugins. To manage them I use vim-plug.

+

To install it:

+ +
+

☞ Note I have two parts in my .vimrc. The first part contains the list of all my plugins. The second part contains the personal preferences I setted for each plugin. I’ll separate each part by ... in the code.

+
+

Survival

+

Colorscheme

+
+Solarized theme +
+

Before anything, you should protect your eyes using a readable and low contrast colorscheme.

+

For this I use solarized dark. To add it, you only have to write this in your ~/.vimrc file:

+
call plug#begin('~/.vim/plugged')
+
+Plug 'altercation/vim-colors-solarized'
+
+call plug#end()
+
+" -- solarized personal conf
+set background=dark
+try
+    colorscheme solarized
+catch
+endtry
+

Minimal hygiene

+

You should be able to see and destroy trailing whitespaces.

+
+Trim whitespaces +
+
Plug 'bronson/vim-trailing-whitespace'
+

You can clean trailing whitespace with :FixWhitespace.

+

And also you should see your 80th column.

+
if (exists('+colorcolumn'))
+    set colorcolumn=80
+    highlight ColorColumn ctermbg=9
+endif
+
+80th column +
+

File Management

+

One of the most important hidden skills in programming is the ability to search and find files in your projects.

+

The majority of people use something like NERDTree. This is the classical left column with a tree of files of your project. I stopped to use this. And you should probably too.

+

I switched to unite. No left column lost. Faster to find files. Mainly it works like Spotlight on OS X.

+

First install ag (the silver search). If you don’t know ack or ag your life is going to be upgraded. This is a simple but essential tool. It is mostly a grep on steroids.

+
" Unite
+"   depend on vimproc
+"   ------------- VERY IMPORTANT ------------
+"   you have to go to .vim/plugin/vimproc.vim and do a ./make
+"   -----------------------------------------
+Plug 'Shougo/vimproc.vim'
+Plug 'Shougo/unite.vim'
+
+...
+
+let g:unite_source_history_yank_enable = 1
+try
+  let g:unite_source_rec_async_command='ag --nocolor --nogroup -g ""'
+  call unite#filters#matcher_default#use(['matcher_fuzzy'])
+catch
+endtry
+" search a file in the filetree
+nnoremap <space><space> :split<cr> :<C-u>Unite -start-insert file_rec/async<cr>
+" reset not it is <C-l> normally
+:nnoremap <space>r <Plug>(unite_restart)
+

Now type space twice. A list of files appears. Start to type some letters of the file you are searching for. Select it, type return and bingo the file opens in a new horizontal split.

+
+Unite example +
+

If something goes wrong just type <space>r to reset the unite cache.

+

Now you are able to search file by name easily and efficiently.

+

Now search text in many files. For this you use ag:

+
Plug 'rking/ag.vim'
+...
+" --- type ° to search the word in all files in the current dir
+nmap ° :Ag <c-r>=expand("<cword>")<cr><cr>
+nnoremap <space>/ :Ag
+

Don’t forget to add a space after the :Ag.

+

These are two of the most powerful shortcut for working in a project. using ° which is nicely positioned on my azerty keyboard. You should use a key close to *.

+

So what ° is doing? It reads the string under the cursor and search for it in all files. Really useful to search where a function is used.

+

If you type <space>/ followed by a string, it will search for all occurrences of this string in the project files.

+

So with this you should already be able to navigate between files very easily.

+

Language Agnostic Plugins

+

Git

+
+Show modified lines +
+

Show which line changed since your last commit.

+
Plug 'airblade/vim-gitgutter'
+

And the “defacto” git plugin:

+
Plug 'tpope/vim-fugitive'
+

You can reset your changes from the latest git commit with :Gread. You can stage your changes with :Gwrite.

+
+Reset changes +
+

Align things

+
Plug 'junegunn/vim-easy-align'
+
+...
+
+" Easy align interactive
+vnoremap <silent> <Enter> :EasyAlign<cr>
+

Just select and type Return then space. Type Return many type to change the alignments.

+

If you want to align the second column, Return then 2 then space.

+
+Easy align example +
+

Basic auto completion: C-n & C-p

+

Vim has a basic auto completion system. The shortcuts are C-n and C-p while you are in insert mode. This is generally good enough in most cases. For example when I open a file not in my configured languages.

+

Haskell

+

My current Haskell programming environment is great!

+

Each time I save a file, I get a comment pointing to my errors or proposing me how to improve my code.

+

So here we go:

+
+

☞ Don’t forget to install ghc-mod with: cabal install ghc-mod

+
+
" ---------- VERY IMPORTANT -----------
+" Don't forget to install ghc-mod with:
+" cabal install ghc-mod
+" -------------------------------------
+
+Plug 'scrooloose/syntastic'             " syntax checker
+" --- Haskell
+Plug 'yogsototh/haskell-vim'            " syntax indentation / highlight
+Plug 'enomsg/vim-haskellConcealPlus'    " unicode for haskell operators
+Plug 'eagletmt/ghcmod-vim'
+Plug 'eagletmt/neco-ghc'
+Plug 'Twinside/vim-hoogle'
+Plug 'pbrisbin/html-template-syntax'    " Yesod templates
+
+...
+
+" -------------------
+"       Haskell
+" -------------------
+let mapleader="-"
+let g:mapleader="-"
+set tm=2000
+nmap <silent> <leader>ht :GhcModType<CR>
+nmap <silent> <leader>hh :GhcModTypeClear<CR>
+nmap <silent> <leader>hT :GhcModTypeInsert<CR>
+nmap <silent> <leader>hc :SyntasticCheck ghc_mod<CR>:lopen<CR>
+let g:syntastic_mode_map={'mode': 'active', 'passive_filetypes': ['haskell']}
+let g:syntastic_always_populate_loc_list = 1
+nmap <silent> <leader>hl :SyntasticCheck hlint<CR>:lopen<CR>
+
+" Auto-checking on writing
+autocmd BufWritePost *.hs,*.lhs GhcModCheckAndLintAsync
+
+"  neocomplcache (advanced completion)
+autocmd BufEnter *.hs,*.lhs let g:neocomplcache_enable_at_startup = 1
+function! SetToCabalBuild()
+    if glob("*.cabal") != ''
+        set makeprg=cabal\ build
+    endif
+endfunction
+autocmd BufEnter *.hs,*.lhs :call SetToCabalBuild()
+
+" -- neco-ghc
+let $PATH=$PATH.':'.expand("~/.cabal/bin")
+

Just enjoy!

+
+hlint on save +
+

I use - for my leader because I use , a lot for its native usage.

+
    +
  • -ht will highlight and show the type of the block under the cursor.
  • +
  • -hT will insert the type of the current block.
  • +
  • -hh will unhighlight the selection.
  • +
+
+Auto typing on save +
+

Clojure

+
+Rainbow parenthesis +
+

My main language at work is Clojure. And my current vim environment is quite good. I lack the automatic integration to lein-kibit thought. If I have the courage I might do it myself one day. But due to the very long startup time of clojure, I doubt I’ll be able to make a useful vim plugin.

+

So mainly you’ll have real rainbow-parenthesis (the default values are broken for solarized).

+

I used the vim paredit plugin before. But it is too restrictive. Now I use sexp which feel more coherent with the spirit of vim.

+
" " -- Clojure
+Plug 'kien/rainbow_parentheses.vim'
+Plug 'guns/vim-clojure-static'
+Plug 'guns/vim-sexp'
+Plug 'tpope/vim-repeat'
+Plug 'tpope/vim-fireplace'
+
+...
+
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesActivate
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadRound
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadSquare
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadBraces
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl setlocal iskeyword+=?,-,*,!,+,/,=,<,>,.,:
+" -- Rainbow parenthesis options
+let g:rbpt_colorpairs = [
+	\ ['darkyellow',  'RoyalBlue3'],
+	\ ['darkgreen',   'SeaGreen3'],
+	\ ['darkcyan',    'DarkOrchid3'],
+	\ ['Darkblue',    'firebrick3'],
+	\ ['DarkMagenta', 'RoyalBlue3'],
+	\ ['darkred',     'SeaGreen3'],
+	\ ['darkyellow',  'DarkOrchid3'],
+	\ ['darkgreen',   'firebrick3'],
+	\ ['darkcyan',    'RoyalBlue3'],
+	\ ['Darkblue',    'SeaGreen3'],
+	\ ['DarkMagenta', 'DarkOrchid3'],
+	\ ['Darkblue',    'firebrick3'],
+	\ ['darkcyan',    'SeaGreen3'],
+	\ ['darkgreen',   'RoyalBlue3'],
+	\ ['darkyellow',  'DarkOrchid3'],
+	\ ['darkred',     'firebrick3'],
+	\ ]
+

Working with Clojure will becomre quite smoother. You can eval any part of your code, you must launch a Clojure REPL manually in another terminal thought.

+

Last words

+

I hope it will be useful.

+

Last but not least, if you want to use my vim configuration you can get it here:

+

github.com/yogsototh/vimrc

]]>
+
+ + Safer Haskell Install + + http://yannesposito.com/Scratch/en/blog/Safer-Haskell-Install/index.html + 2014-08-16T00:00:00Z + 2014-08-16T00:00:00Z + +to Haskell and Beyond!!! + +
+

tl;dr: Install Haskell (OS X and Linux only) by pasting the following in your terminal:

+
curl https://raw.githubusercontent.com/yogsototh/install-haskell/master/install-haskell.sh | sudo zsh
+

update (28 march 2015): I now use Haskell LTS instead of a random stackage version.

+

If you are on windows, just download the Haskell Platform and follow the instruction to use Haskell LTS.

+

If you want to know the why and the how; you should read the entire article.

+
+

Why?

+

The main weakness of Haskell as nothing to do with the language itself but with its ecosystem1.

+

The main problem I’ll try to address is the one known as cabal hell. The community is really active in fixing the issue. I am very confident that in less than a year this problem will be one of the past. But to work today, I provide an install method that should reduce greatly two effects of cabal hell:

+
    +
  • dependency error
  • +
  • lost time in compilation (poor polar bears)
  • +
+

With my actual installation method, you should minimize your headache and almost never hit a dependency error. But there could exists some. If you encounter any dependency error, ask gently to the package manager to port its package to stackage.

+

So to install copy/paste the following three lines in your terminal:

+
curl https://raw.githubusercontent.com/yogsototh/install-haskell/master/install-haskell.sh | sudo zsh
+

How?

+

You can read the script and you will see that this is quite straightforward.

+
    +
  1. It downloads the latest GHC binary for you system and install it.
  2. +
  3. It does the same with the cabal program.
  4. +
  5. It updates your cabal config file to use Haskell LTS.
  6. +
  7. It enable profiling to libraries and executables.
  8. +
  9. It installs some useful binaries that might cause compilation error if not present.
  10. +
+

As the version of libraries is fixed up until you update the Haskell LTS version, you should never use cabal sandbox. That way, you will only compile each needed library once. The compiled objects/binaries will be in your ~/.cabal directory.

+

Some Last Words

+

This script use the latest Haskell LTS. So if you use this script at different dates, the Haskell LTS might have changed.

+

While it comes to cabal hell, some solutions are sandboxes and nix. Unfortunately, sandboxes didn’t worked good enough for me after some time. Furthermore, sandboxes forces you to re-compile everything by project. If you have three yesod projects for example it means a lot of time and CPU. Also, nix didn’t worked as expected on OS X. So fixing the list of package to a stable list of them seems to me the best pragmatic way to handle the problem today.

+

From my point of view, Haskell LTS is the best step in the right direction. The actual cabal hell problem is more a human problem than a tool problem. This is a bias in most programmer to prefer resolve social issues using tools. There is nothing wrong with hackage and cabal. But for a package manager to work in a static typing language as Haskell, packages must work all together. This is a great strength of static typed languages that they ensure that a big part of the API between packages are compatible. But this make the job of package managing far more difficult than in dynamic languages.

+

People tend not to respect the rules in package numbering2. They break their API all the time. So we need a way to organize all of that. And this is precisely what Haskell LTS provide. A set of stable packages working all together. So if a developer break its API, it won’t work anymore in stackage. And whether the developer fix its package or all other packages upgrade their usage. During this time, Haskell LTS end-users will be able to develop without dependency issues.

+
+

+The image of the cat about to jump that I slightly edited can found here +

+
+
+
    +
  1. By ecosystem of a language I mean, the community, the tools, the documentations, the deployment environments, the businesses using the language, etc… Mainly everything that has nothing to do with the detail of a programming language but has to do on how and why we use it.

  2. +
  3. I myself am guilty of such behavior. It was a beginner error.

  4. +
+
]]>
+
+ + Holy Haskell Project Starter + + http://yannesposito.com/Scratch/en/blog/Holy-Haskell-Starter/index.html + 2013-11-14T00:00:00Z + 2013-11-14T00:00:00Z + +Monty Python Holy Grail + +
+

tl;dr: Learn how to start a new Haskell project. Translate a starter tool written in zsh in Haskell using its own result.

+
+

“Good Sir Knight, will you come with me to Camelot, and join us at the Round Table?”

+
+

In order to work properly with Haskell you need to initialize your environment. Typically, you need to use a cabal file, create some test for your code. Both, unit test and propositional testing (random and exhaustive up to a certain depth). You need to use git and generally hosting it on github. Also, it is recommended to use cabal sandboxes. And as bonus, an auto-update tool that recompile and retest on each file save.

+

In this article, we will create such an environment using a zsh script. Then we will write a Haskell project which does the same work as the zsh script. You will then see how to work in such an environment.

+

If you are starting to understand Haskell but consider yourself a beginner, this tutorial will show you how to make a real application using quite surprisingly a lot of features:

+
    +
  • use colorized output
  • +
  • interact with a user in command line
  • +
  • read/write files
  • +
  • kind of parse a file (in fact, simply split it)
  • +
  • use a templating system (mustache: fill a data structure, write files)
  • +
  • make a HTTP GET request then parse the JSON answer and use it
  • +
  • use random
  • +
  • create a cabal package
  • +
  • add and use non source files to a cabal package
  • +
  • Test your code (both unit testing and property testing)
  • +
+

zsh is by its nature more suitable to file manipulation. But the Haskell code is clearly more organized while quite terse for a multi-purpose language.

+

holy-project is on hackage. It can be installed with cabal update && cabal install holy-project.

+
+

I recently read this excellent article: How to Start a New Haskell Project.

+

While the article is very good, I lacked some minor informations1. Inspired by it, I created a simple script to initialize a new Haskell project. During the process I improved some things a bit.

+

If you want to use this script, the steps are:

+
    +
  1. Install Haskell
  2. +
  3. Make sure you have the latest cabal-install (at least 1.18)
  4. +
+ +
    +
  1. Download and run the script
  2. +
+ +

What does this script do that cabal init doesn’t do?

+
    +
  • Use cabal sandbox
  • +
  • It initialize git with the right .gitignore file.
  • +
  • Use tasty to organize your tests (HUnit, QuickCheck and SmallCheck).
  • +
  • Use -Wall for ghc compilation.
  • +
  • Will make references to Holy Grail
  • +
  • Search your default github username via github api.
  • +
+

zsh really?

+
+French insult +
+

Developing the script in zsh was easy. But considering its size, it is worth to rewrite it in Haskell. Furthermore, it will be a good exercise.

+

Patricide

+

In a first time, we initialize a new Haskell project with holy-haskell.sh:

+
+> ./holy-haskell.sh
+Bridgekeeper: Stop!
+Bridgekeeper: Who would cross the Bridge of Death
+Bridgekeeper: must answer me these questions three,
+Bridgekeeper: ere the other side he see.
+You: Ask me the questions, bridgekeeper, I am not afraid.
+
+Bridgekeeper: What is the name of your project?
+> Holy project
+Bridgekeeper: What is your name? (Yann Esposito (Yogsototh))
+>
+Bridgekeeper: What is your email? (Yann.Esposito@gmail.com)
+>
+Bridgekeeper: What is your github user name? (yogsototh)
+>
+Bridgekeeper: What is your project in less than ten words?
+> Start your Haskell project with cabal, git and tests.
+Initialize git
+Initialized empty Git repository in .../holy-project/.git/
+Create files
+    .gitignore
+    holy-project.cabal
+    Setup.hs
+    LICENSE (MIT)
+    test/Test.hs
+    test/HolyProject/Swallow/Test.hs
+    src/HolyProject/Swallow.hs
+    test/HolyProject/Coconut/Test.hs
+    src/HolyProject/Coconut.hs
+    src/HolyProject.hs
+    src/Main.hs
+Cabal sandboxing, install and test
+...
+  many compilations lines
+...
+Running 1 test suites...
+Test suite Tests: RUNNING...
+Test suite Tests: PASS
+Test suite logged to: dist/test/holy-project-0.1.0.0-Tests.log
+1 of 1 test suites (1 of 1 test cases) passed.
+All Tests
+  Swallow
+    swallow test:     OK
+  coconut
+    coconut:          OK
+    coconut property: OK
+      148 tests completed
+
+All 3 tests passed
+
+
+
+Bridgekeeper: What... is the air-speed velocity of an unladen swallow?
+You: What do you mean? An African or European swallow?
+Bridgekeeper: Huh? I... I don't know that.
+[the bridgekeeper is thrown over]
+Bridgekeeper: Auuuuuuuuuuuugh
+Sir Bedevere: How do you know so much about swallows?
+You: Well, you have to know these things when you're a king, you know.
+
+

The different steps are:

+
    +
  • small introduction quotes
  • +
  • ask five questions – three question sir…
  • +
  • create the directory for the project
  • +
  • init git
  • +
  • create files
  • +
  • sandbox cabal
  • +
  • cabal install and test
  • +
  • run the test directly in the terminal
  • +
  • small goodbye quotes
  • +
+

Features to note:

+
    +
  • color in the terminal
  • +
  • check some rules on the project name
  • +
  • random message if error
  • +
  • use ~/.gitconfig file in order to provide a default name and email.
  • +
  • use the github API which returns JSON to get the default github user name.
  • +
+

So, apparently nothing too difficult to achieve.

+

We should now have an initialized Haskell environment for us to work. The first thing you should do, is to go into this new directory and launch ‘./auto-update’ in some terminal. I personally use tmux on Linux or the splits in iTerm 2 on Mac OS X. Now, any modification of a source file will relaunch a compilation and a test.

+

The dialogs

+
+Bridge of Death +
+

To print the introduction text in zsh:

+ +

In the first Haskell version I don’t use colors. We see we can almost copy/paste. I just added the types.

+ +

Now let’s just add the colors using the ansi-terminal package. So we have to add ansi-terminal as a build dependency in our cabal file.

+

Edit holy-project.cabal to add it.

+
...
+build-depends:  base >=4.6 && <4.7
+                , ansi-terminal
+...
+

Now look at the modified Haskell code:

+ +

We could put this code in src/Main.hs. Declare a main function:

+ +

Make cabal install and run cabal run (or ./.cabal-sandbox/bin/holy-project). It works!

+

Five Questions – Three questions Sir!

+
+Bring out your dead! +
+

In order to ask questions, here is how we do it in shell script:

+ +

If we want to abstract things a bit, the easiest way in shell is to use a global variable2 which will get the value of the user input like this:

+ +

In Haskell we won’t need any global variable:

+ +

Now our main function might look like:

+ +

You could test it with cabal install and then ./.cabal-sandbox/bin/holy-project.

+

We will see later how to guess the answer using the .gitconfig file and the github API.

+

Using answers

+
+Castle of Aaaaarrrr???? +
+

Create the project name

+

I don’t really like the ability to use capital letter in a package name. So in shell I transform the project name like this:

+ +

In order to achieve the same result in Haskell (don’t forget to add the split package):

+ +

One important thing to note is that in zsh the transformation occurs on strings but in haskell we use list as intermediate representation:

+
zsh:
+"Holy grail" ==( ${project:gs/ /-/} )=> "Holy-grail"
+             ==( ${project:l}       )=> "holy-grail"
+
+haskell
+"Holy grail" ==( map toLower     )=> "holy grail"
+             ==( splitOneOf " -" )=> ["holy","grail"]
+             ==( intercalate "-" )=> "holy-grail"
+

Create the module name

+

The module name is a capitalized version of the project name where we remove dashes.

+ + +

The haskell version is made by hand where zsh already had a capitalize operation on string with many words. Here is the difference between the shell and haskell way (note I splitted the effect of concatMap as map and concat):

+
shell:
+"Holy-grail" ==( sed 's/-/ /g' )=> "Holy grail"
+             ==( ${(C)str}     )=> "Holy Grail"
+             ==( sed 's/ //g'  )=> "HolyGrail"
+
+haskell:
+"Holy-grail" ==( splitOneOf " -"    )=> ["Holy","grail"]
+             ==( map capitalizeWord )=> ["Holy","Grail"]
+             ==( concat             )=> "HolyGrail"
+

As the preceding example, in shell we work on strings while Haskell use temporary lists representations.

+

Check the project name

+

Also I want to be quite restrictive on the kind of project name we can give. This is why I added a check function.

+ +

Which verify the project name is not empty and use only letter, numbers and dashes:

+ +

Create the project

+
+Giant with three heads and mustaches +
+

Making a project will consists in creating files and directories whose name and content depends on the answer we had until now.

+

In shell, for each file to create, we used something like:

+ +

In Haskell, while possible, we shouldn’t put the file content in the source code. We have a relatively easy way to include external file in a cabal package. This is what we will be using.

+

Furthermore, we need a templating system to replace small part of the static file by computed values. For this task, I choose to use hastache, a Haskell implementation of Mustache templates3.

+

Add external files in a cabal project

+

Cabal provides a way to add files which are not source files to a package. You simply have to add a Data-Files: entry in the header of the cabal file:

+
data-files: scaffold/LICENSE
+            , scaffold/Setup.hs
+            , scaffold/auto-update
+            , scaffold/gitignore
+            , scaffold/interact
+            , scaffold/project.cabal
+            , scaffold/src/Main.hs
+            , scaffold/src/ModuleName.hs
+            , scaffold/src/ModuleName/Coconut.hs
+            , scaffold/src/ModuleName/Swallow.hs
+            , scaffold/test/ModuleName/Coconut/Test.hs
+            , scaffold/test/ModuleName/Swallow/Test.hs
+            , scaffold/test/Test.hs
+

Now we simply have to create our files at the specified path. Here is for example the first lines of the LICENSE file.

+
The MIT License (MIT)
+
+Copyright (c) {{year}} {{author}}
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+...
+

It will be up to our program to replace the {{year}} and {{author}} at runtime. We have to find the files. Cabal will create a module named Paths_holy_project. If we import this module we have the function genDataFileName at our disposal. Now we can read the files at runtime like this:

+ +

Create files and directories

+

A first remark is for portability purpose we shouldn’t use String for file path. For example on Windows / isn’t considered as a subdirectory character. To resolve this problem we will use FilePath:

+ +

Use Hastache

+

In order to use hastache we can either create a context manually or use generics to create a context from a record. This is the last option we will show here. So in a first time, we need to import some modules and declare a record containing all necessary informations to create our project.

+ + +

Once we have declared this, we should populate our Project record with the data provided by the user. So our main function should look like:

+ +

Finally we could use hastache this way:

+ +

We use external files in mustache format. We ask question to our user to fill a data structure. We use this data structure to create a context. Hastache use this context with the external files to create the project files.

+

Git and Cabal

+
+Tim +
+

We need to initialize git and cabal. For this we simply call external command with the system function.

+ +

Ameliorations

+

Our job is almost finished. Now, we only need to add some nice feature to make the application more enjoyable.

+

Better error message

+
+Rabbit +
+

The first one would be to add a better error message.

+ +

And also update where this can be called

+ +

Use .gitconfig

+

We want to retrieve the ~/.gitconfig file content and see if it contains a name and email information. We will need to access to the HOME environment variable. Also, as we use bytestring package for hastache, let’s take advantage of this library.

+ +

We could note I changed the ask function slightly to take a maybe parameter.

+ +

Concerning the parsing of .gitconfig, it is quite minimalist.

+ +

We could notice, getNameAndMail doesn’t read the full file and stop at the first occurrence of name and mail.

+

Use the github API

+
+Coconut and Swallow +
+

The task seems relatively easy, but we’ll see there will be some complexity hidden. Make a request on https://api.github.com/search/users?q=<email>. Parse the JSON and get the login field of the first item.

+

So the first problem to handle is to connect an URL. For this we will use the http-conduit package.

+

Generally, for simple request, we should use:

+ +

But, after some research, I discovered we must declare an User-Agent in the HTTP header to be accepted by the github API. So we have to change the HTTP Header, and our code became slightly more complex:

+ +

So now, we have a String containing a JSON representation. In javascript we would have used login=JSON.parse(body).items[0].login. How does Haskell will handle it (knowing the J in JSON is for Javascript)?

+

First we will need to add the lens-aeson package and use it that way:

+ +

It looks ugly, but it’s terse. In fact each function (^?), key and nth has some great mathematical properties and everything is type safe. Unfortunately I had to make my own jsonValueToString. I hope I simply missed a simpler existing function.

+

You can read this article on lens-aeson and prisms to know more.

+

Concurrency

+
+Priests +
+

We now have all the feature provided by the original zsh script shell. But here is a good occasion to use some Haskell great feature.

+

We will launch the API request sooner and in parallel to minimize our wait time:

+
import Control.Concurrent
+...
+main :: IO ()
+main = do
+    intro
+    gitconfig <- safeReadGitConfig
+    let (name,email) = getNameAndMail gitconfig
+    earlyhint <- newEmptyMVar
+    maybe   (putMVar earlyhint Nothing) -- if no email found put Nothing
+            (\hintmail -> do  -- in the other case request the github API
+                forkIO (putMVar earlyhint =<< getGHUser hintmail)
+                return ())
+            email
+    project <- ask "project name" Nothing
+    ioassert (checkProjectName project)
+             "Use only letters, numbers, spaces and dashes please"
+    let projectname = projectNameFromString project
+        modulename  = capitalize project
+    in_author       <- ask "name" name
+    in_email        <- ask "email" email
+    ghUserHint      <- if maybe "" id email /= in_email
+                            then getGHUser in_email
+                            else takeMVar earlyhint
+    in_ghaccount    <- ask "github account" ghUserHint
+    in_synopsis     <- ask "project in less than a dozen word?" Nothing
+    current_year    <- getCurrentYear
+    createProject $ Project projectname modulename in_author in_email
+                            in_ghaccount in_synopsis current_year
+    end
+

While it might feel a bit confusing, it is in fact quite simple.

+
    +
  1. declare an MVar. Mainly a variable which either is empty or contains something.
  2. +
  3. If we didn’t found any email hint, put Nothing in the MVar.
  4. +
  5. If we have an email hint, ask on the github API in a new process and once finished put the result in the MVar.
  6. +
  7. If the user enter an email different from the hint email, then just request the github api now.
  8. +
  9. If the user enter the same email, then wait for the MVar to be filled and ask the next question with the result.
  10. +
+

If you have a github account and had set correctly your .gitconfig, you might not even wait.

+

Project Structure

+

We have a working product. But, I don’t consider our job finished. The code is about 335 lines.

+

Considering that we:

+
    +
  • have 29 lines of import and 52 lines of comments (rest 255 lines)
  • +
  • ask questions
  • +
  • use a templating system to generate files
  • +
  • call an asynchronous HTTP request
  • +
  • parse JSON
  • +
  • parse .gitconfig
  • +
  • use colored output
  • +
+

This is quite few.

+

Modularizing

+
+The Black Knight +
+

For short programs it is not obvious to split them into different modules. But my personal preference is to split it anyway.

+

First we put all content of src/Main.hs in src/HolyProject.hs. We rename the main function by holyStarter. And our src/Main.hs should contains:

+ +

Of course you have to remember to rename the module of src/HolyProject.hs. I separated all functions in different submodules:

+
    +
  • HolyProject.GitConfig +
      +
    • getNameAndMailFromGitConfig: retrieve name an email from .gitconfig file
    • +
  • +
  • HolyProject.GithubAPI +
      +
    • searchGHUser: retrieve github user name using github API.
    • +
  • +
  • HolyProject.MontyPython +
      +
    • bk: bridge keeper speaks
    • +
    • you: you speak
    • +
    • ask: Ask a question and wait for an answer
    • +
  • +
  • HolyProject.StringUtils: String helper functions +
      +
    • projectNameFromString
    • +
    • capitalize
    • +
    • checkProjectName
    • +
  • +
+

The HolyProject.hs file contains mostly the code that ask questions, show errors and copy files using hastache.

+

One of the benefits in modularizing the code is that our main code is clearer. Some functions are declared only in a module and are not exported. This help us hide technical details. For example, the modification of the HTTP header to use the github API.

+

Documenting

+
+The Holy Grenade +
+

We didn’t take much advantage of the project structure yet. A first thing is to generate some documentation. Before most function I added comment starting with -- |. These comment will be used by haddock to create a documentation. First, you need to install haddock manually.

+ +

Be sure to have haddock in your PATH. You could for example add it like this:

+ +

And if you are at the root of your project you’ll get it. And now just launch:

+ +

And magically, you’ll have a documentation in dist/doc/html/holy-project/index.html.

+

Tests

+

While the Haskell static typing is quite efficient to prevent entire classes of bugs, Haskell doesn’t discard the need to test to minimize the number of bugs.

+

Unit Testing with HUnit

+
+A Witch! A Witch! +
+

It is generally said to test we should use unit testing for code in IO and QuickCheck or SmallCheck for pure code.

+

A unit test example on pure code is in the file test/HolyProject/Swallow/Test.hs:

+ +

Note swallow is (++). We group tests by group. Each group can contain some test suite. Here we have a test suite with only one test. The (@=?) verify the equality between its two parameters.

+

So now, we could safely delete the directory test/HolyProject/Swallow and the file src/HolyProject/Swallow.hs. And we are ready to make our own real world unit test. We will first test the module HolyProject.GithubAPI. Let’s create a file test/HolyProject/GithubAPI/Test.hs with the following content:

+ +

You have to modify your cabal file. More precisely, you have to add HolyProject.GithubAPI in the exposed modules of the library secion). You also have to update the test/Test.hs file to use GithubAPI instead of Swallow.

+

So we have our example of unit testing using IO. We search the github nickname for some people I know and we verify github continue to give the same answer as expected.

+

Property Testing with SmallCheck and QuickCheck

+
+My name is Zoot. Just Zoot +
+

When it comes to pure code, a very good method is to use QuickCheck and SmallCheck. SmallCheck will verify all cases up to some depth about some property. While QuickCheck will verify some random cases.

+

As this kind of verification of property is mostly doable on pure code, we will test the StringUtils module.

+

So don’t forget to declare HolyProject.StringUtils in the exposed modules in the library section of your cabal file. Remove all references to the Coconut module.

+

Modify the test/Test.hs to remove all references about Coconut. Create a test/HolyProject/StringUtils/Test.hs file containing:

+ +

The result is here:

+
+All Tests
+  StringUtils
+    SC projectNameFromString idempotent: OK
+      206 tests completed
+    SC capitalize idempotent:            OK
+      1237 tests completed
+    QC projectNameFromString idempotent: FAIL
+      *** Failed! Falsifiable (after 19 tests and 5 shrinks):
+      "a a"
+      Use --quickcheck-replay '18 913813783 2147483380' to reproduce.
+  GithubAPI
+    Yann:                                OK
+    Jasper:                              OK
+
+1 out of 5 tests failed
+
+

The test fail, but this is not an error. Our capitalize function shouldn’t be idempotent. I simply added this test to show what occurs when a test fail. If you want to look more closely to the error you could do this:

+ +

It is important to use ./interact instead of ghci. Because we need to tell ghci how to found the package installed.

+

Apparently, SmallCheck didn’t found any counter example. I don’t know how it generates Strings and using deeper search is really long.

+

Conclusion

+
+Rabbit +
+

Congratulation!

+

Now you could start programming in Haskell and publish your own cabal package.

+
+
+
    +
  1. For example, you have to install the test libraries manually to use cabal test.

  2. +
  3. There is no easy way to do something like name=$(ask name). Simply because $(ask name) run in another process which doesn’t get access to the standard input

  4. +
  5. Having a good level of power in templates is very difficult. imho Mustache has made the best compromise.

  6. +
+
]]>
+
+ + Parsec Presentation + + http://yannesposito.com/Scratch/en/blog/Parsec-Presentation/index.html + 2013-10-09T00:00:00Z + 2013-10-09T00:00:00Z + AST
+

+
+

tl;dr: Short introduction to Parsec for beginner.

+
+
    +
  • The html presentation is here.
  • +
+
+() () () () () ( +) ( +

) () () () () () () () () ()

+
+ +
+
+

+Parsec +

+by Yann Esposito + + +
+
+
+

+Parsing +

+

+Latin pars (ōrātiōnis), meaning part (of speech). +

+
    +
  • +analysing a string of symbols +
  • +
  • +formal grammar. +
  • +
+
+
+

+Parsing in Programming Languages +

+

+Complexity: +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+Method + +Typical Example + +Output Data Structure +
+Splitting + +CSV + +Array, Map +
+Regexp + +email + +
    +
  • Fixed Layout Tree +
+Parser + +Programming language + +
    +
  • Most Data Structure +
+
+
+

+Parser & culture +

+

+In Haskell Parser are really easy to use. +

+

+Generally: +

+
    +
  • +In most languages: split then regexp then parse +
  • +
  • +In Haskell: split then parse +
  • +
+
+
+

+Parsing Example +

+

+From String: +

+
(1+3)*(1+5+9)
+

+To data structure: +

+

+AST
+

+
+
+

+Parsec +

+
+

+Parsec lets you construct parsers by combining high-order Combinators to create larger expressions. +

+

+Combinator parsers are written and used within the same programming language as the rest of the program. +

+

+The parsers are first-class citizens of the languages […]" +

+

+Haskell Wiki +

+
+
+
+

+Parser Libraries +

+

+In reality there are many choices: +

+ + + + + + + + + + + + + + + +
+attoparsec + +fast +
+Bytestring-lexing + +fast +
+Parsec 3 + +powerful, nice error reporting +
+
+
+

+Haskell Remarks (1) +

+

+spaces are meaningful +

+
f x   -- ⇔ f(x) in C-like languages
+f x y -- ⇔ f(x,y)
+
+
+

+Haskell Remarks (2) +

+

+Don’t mind strange operators (<*>, <$>).
Consider them like separators, typically commas.
They are just here to deal with types. +

+

+Informally: +

+
toto <$> x <*> y <*> z
+-- ⇔ toto x y z
+-- ⇔ toto(x,y,z) in C-like languages
+
+
+

+Minimal Parsec Examples +

+
whitespaces = many (oneOf "\t ")
+number = many1 digit
+symbol = oneOf "!#$%&|*+-/:<=>?@^_~"
+
+" \t " – whitespaces on " \t " "" – whitespaces on “32” “32” – number on “32”
+
+
+– number on " ]]>
+
+ + Rational Web Framework Choice + + http://yannesposito.com/Scratch/en/blog/Rational-Web-Framework-Choice/index.html + 2013-08-06T00:00:00Z + 2013-08-06T00:00:00Z + +Main image + +
+

tl;dr: Determine with the most objectivity possible the best(s) web framework(s) depending on your needs. Here are the results. Please note the actual ability to take a rational decision is pretty bad for now.

+
+

This is it.
+You’ve got the next big idea.
+You just need to make a very simple web application.

+

It sounds easy! You just need to choose a good modern web framework, when suddenly:

+
+[Choice Paralysis][choice_paralysis] +
+Choice Paralysis +
+
+

After your brain stack overflowed, you decide to use a very simple methodology. Answer two questions:

+

Which language am I familiar with?
+What is the most popular web framework for this language?

+

Great! This is it.

+

But, you continually hear this little voice.

+
+

“You didn’t made a bad choice, yes. But …
+you hadn’t made the best either.”

+
+

This article try to determine in the most objective and rational way the best(s) web framework(s) depending on your needs. To reach this goal, I will provide a decision tool in the result section.

+

I will use the following methodology:

+

Methodology

+
    +
  1. Model how to make choice +
      +
    1. choose important parameters
    2. +
    3. organize (hierarchize) them
    4. +
    5. write down an objective chooser
    6. +
  2. +
  3. Grab objective quantified informations about web frameworks relatively to choosen parameters
  4. +
  5. Sanitize your data in order to handle imprecisions, lack of informations…
  6. +
  7. Apply the model of choice to your informations
  8. +
+
+

☞ Important Note
+I am far from happy to the actual result. There are a lot of biases, for example in the choice of the parameters. The same can be said about the data I gathered. I am using very imprecise informations. But, as far as I know, this is the only article which use many different parameters to help you choose a web framework.

+

This is why I made a very flexible decision tool:

+

Decision tool.

+
+

Model

+

Here are the important features (properties/parameters) I selected to make the choice:

+
    +
  1. Popularity, which correlate with: +
      +
    • number of tested libraries
    • +
    • facility to find learning material
    • +
    • ability to find another developer to work with
    • +
  2. +
  3. Efficiency, which is generally correlated to: +
      +
    • how much processing power you’ll need per user
    • +
    • maintenance price per user
    • +
    • how long the user will wait to see/update data
    • +
  4. +
  5. Expressiveness, which is generally correlated to: +
      +
    • faster development
    • +
    • flexibility, adaptability
    • +
  6. +
  7. Robustness, which correlate with: +
      +
    • security
    • +
    • fewer bugs
    • +
  8. +
+

Each feature is quite important and mostly independant from each other. I tried to embrace most important topics concerning web frameworks with these four properties. I am fully concious some people might lack another important feature. Nonetheless the methodology used here can be easily replicated. If you lack an important property add it at will and use this choice method.

+

Also each feature is very hard to measure with precision. This is why we will only focus on order of magnitude.

+

For each property a framework could have one of the six possible values: Excellent, Very Good, Good, Medium, Bad or Very Bad

+

So how to make a decision model from these informations?

+

One of the most versatile method is to give a weight for each cluster value. And to select the framework maximizing this score:

+
score(framework) = efficiency + robustness + expressiveness + popularity
+
+

For example:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Expressiveness1071-∞-∞-∞
Popularity554321
Efficiency1086421
Robustness1086421
+

Using this weighted table, that means:

+
    +
  • we discard the three least expressive clusters.
  • +
  • We don’t make any difference between excellent and very good in popularity.
  • +
  • Concerning efficient framework in excellent cluster will have 2 more points than the “very good” cluster.
  • +
+

So for each framework we compute its score relatively to a weighted table. And we select the best(s).

+

Example: Using this hypothetic framework and the preceeding table.

+ + + + + + + + + + + + + + + + + + + +
ExpressivenessPopularityEfficiencyRobustness
yogExcellentVery BadMediumVery Good
+
score(yog) = 10 + 0 + 4 + 8 = 22
+
+

Most needs should be expressed by such a weighted table. In the result section, we will discuss this further.

+

It is now time to try to get these measures.

+

Objective measures

+

None of the four properties I choosen can be measured with perfect precision. But we could get the order of magnitude for each.

+

I tried to focus on the framework only. But it is often easier to start by studying the language first.

+

For example, I have datas about popularity by language and I also have different datas concerning popularity by framework. Even if I use only the framework focused datas in my final decision model, it seemed important to me to discuss about the datas for the languages. The goal is to provide a tool to help decision not to give a decision for you.

+

Popularity

+

RedMonk Programming Language Rankings (January 2013) provide an apparent good measure of popularity. While not perfect the current measure feel mostly right. They create an image using stack overflow and github data. Vertical correspond to the number of questions on stackoverflow. Horizontal correspond to the number of projects on github.

+

If you look at the image, your eye can see about four clusters. The 1 cluster correspond to mainstream languages:

+
+Mainstream Languages Cluster from [RedMonk][redmonk] +
+Mainstream Languages Cluster from RedMonk +
+
+

Most developer know at least one of these language.

+

The second cluster is quite bigger. It seems to correspond to languages with a solid community behind them.

+
+Second tier languages from [RedMonk][redmonk] +
+Second tier languages from RedMonk +
+
+

I don’t get into detail, but you could also see third and fourth tier popular languages.

+

So:

+

Mainstream: JavaScript, Java, PHP, Python, Ruby, C#, C++, C, Objective-C, Perl, Shell

+

Good: Scala, Haskell, Visual Basic, Assembly, R, Matlab, ASP, ActionScript, Coffeescript, Groovy, Clojure, Lua, Prolog

+

Medium: Erlang, Go, Delphi, D, Racket, Scheme, ColdFusion, F#, FORTRAN, Arduino, Tcl, Ocaml

+

Bad: third tier Very Bad: fourth tier

+

I don’t thing I could find easily web frameworks for third or fourth tier languages.

+

For now, I only talked about language popularity. But what about framework popularity? I made a test using number of question on stackoverflow only. Then by dividing by two for each 6 cluster:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework#nb%
ExcellentRubyRails176208100%
Very GoodPythonDjango57385<50%
JavaServlet54139
JavaSpring31641
Node.jsnode.js27243
PHPCodeigniter21503
GroovyGrails20222
GoodRubySinatra8631<25%
PythonFlask7062
PHPLaravel6982
PHPKohana5959
Node.jsExpress5009
MediumPHPCake4554<13%
C♯ServiceStack3838
ScalaPlay3823
JavaWicket3819
DartDart3753
PHPSlim3361
PythonTornado3321
ScalaLift2844
GoGo2689
BadJavaTapestry1197<6%
C♯aspnet1000
HaskellYesod889
PHPSilex750
PHPLithium732
C♯nancy705
Very badJavaGrizzly622<3%
ErlangCowboy568
PerlDancer496
PHPSymphony2491
GoRevel459
ClojureCompojure391
PerlMojolicious376
ScalaScalatra349
ScalaFinagle336
PHPPhalcon299
jsRingo299
JavaGemini276
HaskellSnap263
PerlPlack257
ErlangElli230
JavaDropwizard188
PHPYaf146
JavaPlay1133
Node.jsHapi131
JavaVertx60
ScalaUnfiltered42
Conion18
Clojurehttp-kit17
PerlKelp16
PHPMicromvc13
LuaOpenresty8
C++cpoll-cppsp5
ClojureLuminus3
PHPPhreeze1
+

As we can see, our framework popularity indicator can be quite different from its language popularity. For now I didn’t found a nice way to merge the results from RedMonk with these one. So I’ll use these unperfect one. Hopefully the order of magninute is mostly correct for most framework.

+

Efficiency

+

Another objective measure is efficiency. We all know benchmarks are all flawed. But they are the only indicators concerning efficiency we have.

+

I used the benchmark from benchmarksgame. Mainly, there are five clusters:

+ + + + + + + + + + + + + + + + + + + + + + + +
1x→2xC, C++
2x→3xJava 7, Scala, OCamL, Haskell, Go, Common LISP
3x→10xC♯, Clojure, Racket, Dart
10x→30xErlang
30x→PHP, Python, Perl, Ruby, JRuby
+

Remarks concerning some very slow languages:

+
    +
  • PHP ; huge variations, can be about 1.5x C speed in best case.
  • +
  • Python ; huge variations, can be about 1.5x C speed in best case
  • +
  • Perl ; Can be about 3x C speed in best case
  • +
  • Ruby, JRuby ; mostly very slow.
  • +
+

This is a first approach. The speed of the language for basic benchmarks. But, here we are interrested in web programming. Fortunately techempower has made some tests focused on most web frameworks:

+

Web framework benchmarks.

+

These benchmark doesn’t fit well with our needs. The values are certainly quite imprecise to your real usage. The goal is just to get an order of magnitude for each framework. Another problem is the high number of informations.

+

As always, we should remember these informations are also imprecise. So I simply made some classes of efficiency.

+

Remark: I separated the clusters by using power of 2 relatively to the fastest.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework#nbslowness
ExcellentC++cpoll-cppsp114,711
Javgemini105,204
Luaopenresty93,882
Javservlet90,580
C++cpoll-pool89,167
Gogo76,024
Scafinagle68,413
Gorevel66,990
Javrest-express63,209
Very GoodJavwicket48,772>2×
Scascalatra48,594
Cljhttp-kit42,703
Javspring36,643>3×
PHPphp36,605
Javtapestry35,032
Cljcompojure32,088
JSringo31,962
Javdropwizard31,514
Cljluminus30,672
GoodScaplay-slick29,950>4×
Scaunfiltered29,782
Erlelli28,862
Javvertx28,075
JSnodejs27,598
Erlcowboy24,669
Conion23,649
Hklyesod23,304
JSexpress22,856>5×
Scaplay-scala22,372
Jav grizzly-jersey20,550
Pytornado20,372>6×
PHPphalcon18,481
Grvgrails18,467
Prlplack16,647>7×
PHPyaf14,388
MediumJShapi11,235>10×
Javplay19,979
Hklsnap9,196
Prlkelp8,250
Pyflask8,167
Javplay-java7,905
Jav play-java-jpa7,846
PHPmicromvc7,387
Prldancer5,040>20×
Prlmojolicious4,371
JSringo-conv4,249
Pydjango4,026
PHPcodeigniter3,809>30×
BadRbyrails3,445
Scalift3,311
PHPslim3,112
PHPkohana2,378>40×
PHPsilex2,364
Very BadPHPlaravel1,639>60×
PHPphreeze1,410
PHPlithium1,410
PHPfuel1,410
PHPcake1,287>80×
PHPsymfony2879>100×
C#aspnet-mvc871
Rbysinatra561>200×
C#servicestack51
Dardart0
C#nancy0
Prlweb-simple0
+

These are manually made clusters. But you get the idea. Certainly, some framework could jump between two different clusters. So this is something to remember. But as always, the order of magnitude is certainly mostly right.

+

Expressiveness

+

Now, how to objectively measure expressiveness?

+

RedMonk had a very good idea to find an objective (while imprecise) measure of each language expressiveness. Read this article for details.

+

After filtering languages suitable for web development, we end up with some clusters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguages
ExcellentCoffeescript, Clojure, Haskell
Very GoodRacket, Groovy, R, Scala, OCamL, F♯, Erlang, Lisp, Go
MediumPerl, Python, Objective-C, Scheme, Tcl, Ruby
BadLua, Fortran (free-format), PHP, Java, C++, C♯
Very BadAssembly, C, Javascript,
+

Unfortunately there is no information about dart. So I simply give a very fast look at the syntax. As it looked a lot like javascript and js is quite low. I decided to put it close to java.

+

Also an important remark, javascript score very badly here while coffeescript (compiling to js) score “excellent”. So if you intend to use a javascript framework but only with coffescript that should change substantially the score. As I don’t believe it is the standard. Javascript oriented framework score very badly regarding expressiveness.

+
+Click here to show/hide the table for frameworks +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework
ExcellentCljluminus
Cljhttp-kit
Cljcompojure
Hklsnap
Hklyesod
Very GoodErlelli
Erlcowboy
Gogo
Gorevel
Grvgrails
Scalift
Scafinagle
Scascalatra
Scaplay-scala
Scaplay-slick
Scaunfiltered
MediumPrlkelp
Prlplack
Prldancer
Prlweb-simple
Prlmojolicious
Pyflask
Pydjango
Pytornado
Rbyrails
Rbysinatra
BadC#nancy
C#aspnet-mvc
C#servicestack
C++cpoll-pool
C++cpoll-cppsp
Dardart
Javplay1
Javvertx
Javgemini
Javspring
Javwicket
Javservlet
Javtapestry
Javplay-java
Javdropwizard
Javrest-express
Javplay-java-jpa
Javgrizzly-jersey
Luaopenresty
PHPphp
PHPyaf
PHPcake
PHPfuel
PHPslim
PHPsilex
PHPkohana
PHPlaravel
PHPlithium
PHPphalcon
PHPphreeze
PHPmicromvc
PHPsymfony2
PHPcodeigniter
Very BadConion
JShapi
JSringo
JSnodejs
JSexpress
JSringo-conv
+
+

Robustness

+

I couldn’t find any complete study to give the number of bug relatively to each framework/language.

+

But one thing I saw from experience is the more powerful the type system the safest your application is. While the type system doesn’t remove completely the need to test your application a very good type system tend to remove complete classes of bug.

+

Typically, not using pointer help to reduce the number of bugs due to bad references. Also, using a garbage collector, reduce greatly the probability to access unallocated space.

+
+Static Type Properties from [James IRY Blog][typesanalysis] +
+Static Type Properties from James IRY Blog +
+
+

From my point of view, robustness is mostly identical to safety.

+

Here are the clusters:

+ + + + + + + + + + + + + + + + + + + +
ExcellentHaskell, Scheme, Erlang
Very GoodScala, Java, Clojure
GoodRuby, Python, Groovy, javascript, PHP
MediumC++, C#, Perl, Objective-C, Go, C
+

So applying this to frameworks gives the following clusters:

+
+Click here to show/hide the table for frameworks +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework
ExcellentErlelli
Erlcowboy
Hklsnap
Hklyesod
Very GoodCljluminus
Cljhttp-kit
Cljcompojure
Javplay1
Javvertx
Javgemini
Javspring
Javwicket
Javservlet
Javtapestry
Javplay-java
Javdropwizard
Javrest-express
Javplay-java-jpa
Javgrizzly-jersey
Scalift
Scafinagle
Scascalatra
Scaplay-scala
Scaplay-slick
Scaunfiltered
GoodGrvgrails
JShapi
JSringo
JSnodejs
JSexpress
JSringo-conv
Luaopenresty
PHPphp
PHPyaf
PHPcake
PHPfuel
PHPslim
PHPsilex
PHPkohana
PHPlaravel
PHPlithium
PHPphalcon
PHPphreeze
PHPmicromvc
PHPsymfony2
PHPcodeigniter
Pyflask
Pydjango
Pytornado
Rbyrails
Rbysinatra
MediumConion
C#nancy
C#aspnet-mvc
C#servicestack
C++cpoll-pool
C++cpoll-cppsp
Dardart
Gogo
Gorevel
Prlkelp
Prlplack
Prldancer
Prlweb-simple
Prlmojolicious
+
+

The result

+

For the result I initialized the table with my own needs.

+

And I am quite happy it confirms my current choice. I sware I didn’t given yesod any bonus point. I tried to be the most objective and factual as possible.

+

Now, it is up to you to enter your preferences.

+

On each line you could change how important a feature is for you. From essential to unsignificant. Of course you could change the matrix at will.

+

I just show a top 10 frameworks. In order to give a more understandable measure I provide the log of the score.

+ + + + + + + + + + + + + + + + + + + + + + + +
+ +Excellent + +Very good + +Good + +Medium + +Bad + +Very bad + +Importance +
+Expressiveness +
+Popularity +
+Efficiency +
+Robustness +
+
+Click to force refresh +
+
+ +
+ + +

I didn’t had the courage in explaining in what the scoring system is good. Mostly, if you use product instead of sum for the score you could use power of e for the values in the matrix. And you could see the matrix as a probability matrix (each line sum to 1). Which provide a slighly better intuition on whats going on.

+

Remember only that values are exponential. Do not double an already big value for example the effect would be extreme.

+

Conclusion

+

All of this is based as most as I could on objective data. The choice method seems both rather rational and classical. It is now up to you to edit the score matrix to set your needs.

+

I know that in the current state there are many flaws. But it is a first system to help make a choice rationally.

+

I encourage you to go further if you are not satisfied by my method.

+

The source code for the matrix shouldn’t be too hard to read. Just read the source of this webpage. You could change the positionning of some frameworks if you believe I made some mistake by placing them in some bad clusters.

+

So I hope this tool will help you in making your life easier.

]]>
+
+ + Hakyll setup + + http://yannesposito.com/Scratch/en/blog/Hakyll-setup/index.html + 2013-03-16T00:00:00Z + 2013-03-16T00:00:00Z + +Main image + +
+

tl;dr: How I use hakyll. Abbreviations, typography corrections, multi-language, use index.html, etc…

+
+

This website is done with Hakyll.

+

Hakyll can be considered as a minimal cms. But more generally it is a library helping file generation. We can view it as an advanced build system (like make).

+

From the user perspective I blog this way:

+
    +
  1. I open an editor (vim in my case) and edit a markdown file. It looks like this
  2. +
+ +
    +
  1. I open a browser and reload time to time to see the change.
  2. +
  3. Once I finished I’ve written a very minimal script which mainly do a git push. My blog is hosted on github.
  4. +
+

Being short sighted one could reduce the role of Hakyll to:

+
+

create (resp. update) html file when I create (resp. change) a markdown file.

+
+

While it sounds easy, there are a lot of hidden details:

+
    +
  • Add metadatas like keywords.
  • +
  • Create an archive page containing a list of all the posts.
  • +
  • Deal with static files.
  • +
  • Creating an rss feed.
  • +
  • Filter the content with some function.
  • +
  • Dealing with dependencies.
  • +
+

The work of Hakyll is to help you with these. But let’s start with the basic concepts.

+

The concepts and syntax

+
+Overview +
+

For each file you create, you have to provide:

+
    +
  • a destination path
  • +
  • a list of content filters.
  • +
+

First, let’s start with the simplest case: static files (images, fonts, etc…). Generally, you have a source directory (here is the current directory) and a destination directory _site.

+

The Hakyll code is:

+ +

This program will copy static/foo.jpg to _site/static/foo.jpg. I concede this is a bit overkill for a simple cp. Now how to write a markdown file and generate an html one?

+ +

If you create a file posts/foo.md, it will create a file _site/posts/foo.html.

+

If the file posts/foo.md contains

+ +

the file _site/posts/foo.html, will contain

+ +

But horror! _site/posts/cthulhu.html is not a complete html file. It doesn’t have any header nor footer, etc… This is where you use templates. I simply add a new directive in the compile block.

+ +

Now if templates/posts.html contains:

+ +

our cthulhu.html contains (indentation added for readability):

+ +

See, it’s easy But we have a problem. How could we change the title or add keywords?

+

The solution is to use Contexts. For this, we first need to add some metadatas to our markdown1.

+ +

And modify slightly our template:

+ +

As Sir Robin said just before dying before the Bridge of Death:

+
+

“That’s EASY!”

+

Sir Robin, the Not-Quite-So-Brave-As-Sir-Lancelot

+
+

Real customization

+

Now that we understand the basic functionality. How to:

+
    +
  • use SASS?
  • +
  • add keywords?
  • +
  • simplify url?
  • +
  • create an archive page?
  • +
  • create an rss feed?
  • +
  • filter the content?
  • +
  • add abbreviations support?
  • +
  • manage two languages?
  • +
+

Use SASS

+

That’s easy. Simply call the executable using unixFilter. Of course you’ll have to install SASS (gem install sass). And we also use compressCss to gain some space.

+ +

Add keywords

+

In order to help to reference your website on the web, it is nice to add some keywords as meta datas to your html page.

+ +

In order to add keywords, we could not directly use the markdown metadatas. Because, without any, there should be any meta tag in the html.

+

An easy answer is to create a Context that will contains the meta tag.

+ +

Then we pass this Context to the loadAndApplyTemplate function:

+ +
+

☞ Here are the imports I use for this tutorial.

+ +
+

Simplify url

+

What I mean is to use url of the form:

+
http://domain.name/post/title-of-the-post/
+

I prefer this than having to add file with .html extension. We have to change the default Hakyll route behavior. We create another function niceRoute.

+ +

Not too difficult. But! There might be a problem. What if there is a foo/index.html link instead of a clean foo/ in some content?

+

Very simple, we simply remove all /index.html to all our links.

+ +

And we apply this filter at the end of our compilation

+ +

Create an archive page

+

Creating an archive start to be difficult. There is an example in the default Hakyll example. Unfortunately, it assumes all posts prefix their name with a date like in 2013-03-20-My-New-Post.md.

+

I migrated from an older blog and didn’t want to change my url. Also I prefer not to use any filename convention. Therefore, I add the date information in the metadata published. And the solution is here:

+ +

Where templates/archive.html contains

+ +

And base.html is a standard template (simpler than post.html).

+

archiveCtx provide a context containing an html representation of a list of posts in the metadata named posts. It will be used in the templates/archive.html file with $posts$.

+ +

postList returns an html representation of a list of posts given an Item sort function. The representation will apply a minimal template on all posts. Then it concatenate all the results. The template is post-item.html:

+ +

Here is how it is done:

+ +

createdFirst sort a list of item and put it inside Compiler context. We need to be in the Compiler context to access metadatas.

+ +

It wasn’t so easy. But it works pretty well.

+

Create an rss feed

+

To create an rss feed, we have to:

+
    +
  • select only the lasts posts.
  • +
  • generate partially rendered posts (no css, js, etc…)
  • +
+

We could then render the posts twice. One for html rendering and another time for rss. Remark we need to generate the rss version to create the html one.

+

One of the great feature of Hakyll is to be able to save snapshots. Here is how:

+ +

Now for each post there is a snapshot named “content” associated. The snapshots are created before applying a template and after applying pandoc. Furthermore feed don’t need a source markdown file. Then we create a new file from no one. Instead of using match, we use create:

+ +

The feedConfiguration contains some general informations about the feed.

+ +

Great idea certainly steal from nanoc (my previous blog engine)!

+

Filter the content

+

As I just said, nanoc was my preceding blog engine. It is written in Ruby and as Hakyll, it is quite awesome. And one thing Ruby does more naturally than Haskell is regular expressions. I had a lot of filters in nanoc. I lost some because I don’t use them much. But I wanted to keep some. Generally, filtering the content is just a way to apply to the body a function of type String -> String.

+

Also we generally want prefilters (to filter the markdown) and postfilters (to filter the html after the pandoc compilation).

+

Here is how I do it:

+ +

Where

+ +

Now we have a simple way to filter the content. Let’s augment the markdown ability.

+

Add abbreviations support

+

Comparing to LaTeX, a very annoying markdown limitation is the lack of abbreviations.

+

Fortunately we can filter our content. And here is the filter I use:

+ +

It will search for all string starting by ‘%’ and it will search in the Map if there is a corresponding abbreviation. If there is one, we replace the content. Otherwise we do nothing.

+

Do you really believe I type

+ +

each time I write LaTeX?

+

Manage two languages

+

Generally I write my post in English and French. And this is more difficult than it appears. For example, I need to filter the language in order to get the right list of posts. I also use some words in the templates and I want them to be translated.

+ +

First I create a Map containing all translations.

+ +

Then I create a context for all key:

+ +

Conclusion

+

The full code is here. And except from the main file, I use literate Haskell. This way the code should be easier to understand.

+

If you want to know why I switched from nanoc:

+

My preceding nanoc website was a bit too messy. So much in fact, that the dependency system recompiled the entire website for any change.

+

So I had to do something about it. I had two choices:

+
    +
  1. Correct my old code (in Ruby)
  2. +
  3. Duplicate the core functionalities with Hakyll (in Haskell)
  4. +
+

I added too much functionalities in my nanoc system. Starting from scratch (almost) remove efficiently a lot of unused crap.

+

So far I am very happy with the switch. A complete build is about 4x faster. I didn’t broke the dependency system this time. As soon as I modify and save the markdown source, I can reload the page in the browser.

+

I removed a lot of feature thought. Some of them will be difficult to achieve with Hakyll. A typical example:

+

In nanoc I could take a file like this as source:

+ +

And it will create a file foo.hs which could then be downloaded.

+ +
+
+
    +
  1. We could also add the metadatas in an external file (foo.md.metadata).

  2. +
+
]]>
+
+ + Social link the right way + + http://yannesposito.com/Scratch/en/blog/Social-link-the-right-way/index.html + 2013-03-14T00:00:00Z + 2013-03-14T00:00:00Z + +Main image + + +

The problem

+

Ever been on a website and want to tweet about it? Fortunately, the website might have a button to help you. But do you really know what this button do?

+

The “Like”, “Tweet” and “+1” buttons will call a javascript. It will get access to your cookies. It helps the provider of the button to know who you are.

+

In plain English, the “+1” button will inform Google you are visiting the website, even if you don’t click on “+1”. The same is true for the “like” button for facebook and the “tweet this” button for twitter.

+

The problem is not only a privacy issue. In fact (sadly imho) this isn’t an issue for most people. These button consume computer ressources. Far more than a simple link. It thus slow down a bit the computer and consume energy. These button could also slow down the rendering of your web page.

+

Another aspect is their design. Their look and feel is mostly imposed by the provider.

+

The most problematic aspect in my opinion is to use a third party js on your website. What if tomorrow twitter update their tweet button? If the upgrade break something for only a minority of people, they won’t fix it. This could occur anytime without any notification. They just have to add a document.write in their js you call asynchronously and BAM! Your website is just an empty blank page. And as you call many external ressources, it can be very difficult to find the origin of the problem.

+

Using social network buttons:

+
    +
  • Pros: +
      +
    • help user share your website,
    • +
    • can provide a popularity indicator to your users.
    • +
  • +
  • Cons: +
      +
    • you help tracking your users,
    • +
    • generally doesn’t follow the design of your website,
    • +
    • use more computer ressources,
    • +
    • slow down your website,
    • +
    • executing third party js can break things silently.
    • +
  • +
+

Solutions

+

I will provide you two solutions with the following properties:

+
    +
  • Pros: +
      +
    • help user share your website,
    • +
    • doesn’t follow your user,
    • +
    • use almost no computer ressource,
    • +
    • doesn’t slow down your website,
    • +
    • doesn’t execute any third party js on your website.
    • +
  • +
  • Cons: +
      +
    • doesn’t provide any popularity information.
    • +
  • +
+

Solution 1 (no js):

+ +

But you have to replace $url$ by the current url.

+

Solution 2 (Just copy/paste):

+

If you don’t want to write the url yourself, you could use some minimal js:

+ +

Here is the result:

+
+ + +
+

Good looking solutions

+

If you don’t want just text but nice icons. You have many choices:

+
    +
  • Use images <img src="..."/> in the links.
  • +
  • Use icon fonts
  • +
+

As the first solution is pretty straightforward, I’ll explain the second one.

+
    +
  1. Download the icon font here
  2. +
  3. put the font file(s) at some place (here ‘fonts/social_font.ttf’ relatively to your css file)
  4. +
  5. Add this to your css
  6. +
+ +

Now add this to your html:

+

Solution 1 (without js):

+ +

Solution 2 (same with a bit more js):

+ +

Here is the result:

+
+
+

· ·

+
+ +
+

Conclusion

+
    +
  1. You get your design back,
  2. +
  3. You stop to help tracking people,
  4. +
  5. You use less computer ressources and more generally power ressources which is good for the planet,
  6. +
  7. Your web pages will load faster.
  8. +
+

ps: On my personal website I continue to use Google analytics. Therefore, Google (and only Google, not facebook nor twitter) can track you here. But I might change this in the future.

]]>
+
+ + 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.

+
+]]>
+ + + diff --git a/src/Scratch/en/blog/index.html b/src/Scratch/en/blog/index.html new file mode 100644 index 0000000..bf5c3e2 --- /dev/null +++ b/src/Scratch/en/blog/index.html @@ -0,0 +1,364 @@ + + + + + YBlog - Blog + + + + + + + + + + + + + + + +
+ + +
+

Blog

+
+
+
+
+ +

Archive

+ + + +
+ +
+
+ +
+ Yann Esposito© +
+
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/blog/programming-language-experience/index.html b/src/Scratch/en/blog/programming-language-experience/index.html new file mode 100644 index 0000000..eb91212 --- /dev/null +++ b/src/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.

+ +

I also remember I copied some game source code from some magazine. Most lines were like:

+ +

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:

+ +

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. To conclude, I’d say, C++ is very good if you work alone or with a fixed subset of its features.

+

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© 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:

+ +

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. 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:

+ +

It will place the point AA between the point A and B. More precisely at the barycenter (2xA + B)/3.

+ +

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

+
+
+ + + +
+
+ Published on 2011-09-28 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/latest/index.html b/src/Scratch/en/latest/index.html new file mode 100644 index 0000000..52b2fc3 --- /dev/null +++ b/src/Scratch/en/latest/index.html @@ -0,0 +1,90 @@ + + + + + YBlog - latest + + + + + + + + + + + + + + + +
+ + +
+

latest

+
+
+
+
+ +
+ +
+
+ +
+ Yann Esposito© +
+
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/rss/index.html b/src/Scratch/en/rss/index.html new file mode 100644 index 0000000..5df8d54 --- /dev/null +++ b/src/Scratch/en/rss/index.html @@ -0,0 +1,104 @@ + + + + + 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 + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/softwares/index.html b/src/Scratch/en/softwares/index.html new file mode 100644 index 0000000..d32f558 --- /dev/null +++ b/src/Scratch/en/softwares/index.html @@ -0,0 +1,94 @@ + + + + + YBlog - Softwares + + + + + + + + + + + + + + + +
+ + +
+

Softwares

+
+
+
+ + +
+
+ +
+ Yann Esposito© +
+
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/softwares/yaquabubbles/index.html b/src/Scratch/en/softwares/yaquabubbles/index.html new file mode 100644 index 0000000..e6389be --- /dev/null +++ b/src/Scratch/en/softwares/yaquabubbles/index.html @@ -0,0 +1,92 @@ + + + + + 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 + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/softwares/yclock/index.html b/src/Scratch/en/softwares/yclock/index.html new file mode 100644 index 0000000..ca032f3 --- /dev/null +++ b/src/Scratch/en/softwares/yclock/index.html @@ -0,0 +1,92 @@ + + + + + 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 + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/softwares/ypassword/index.html b/src/Scratch/en/softwares/ypassword/index.html new file mode 100644 index 0000000..4fffabf --- /dev/null +++ b/src/Scratch/en/softwares/ypassword/index.html @@ -0,0 +1,102 @@ + + + + + YBlog - YPassword + + + + + + + + + + + + + + + +
+ + +
+

YPassword

+
+
+
+
+

Easy, Secure and Portable way to manage your web passwords.

+

Here done in elm:

+ +

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 + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/softwares/ypassword/iphoneweb/index.html b/src/Scratch/en/softwares/ypassword/iphoneweb/index.html new file mode 100644 index 0000000..4eba8e7 --- /dev/null +++ b/src/Scratch/en/softwares/ypassword/iphoneweb/index.html @@ -0,0 +1,96 @@ + + + + + YBlog - YPassword + + + + + + + + + + + + + + + +
+ + +
+

YPassword

+
+
+
+
+
+ +
+
+ +
+
+ +
+ Yann Esposito© +
+
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/softwares/ypassword/web/index.html b/src/Scratch/en/softwares/ypassword/web/index.html new file mode 100644 index 0000000..8a2554d --- /dev/null +++ b/src/Scratch/en/softwares/ypassword/web/index.html @@ -0,0 +1,96 @@ + + + + + YBlog - YPassword + + + + + + + + + + + + + + + +
+ + +
+

YPassword

+
+
+
+
+
+ +
+
+ +
+
+ +
+ Yann Esposito© +
+
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/en/validation/index.html b/src/Scratch/en/validation/index.html new file mode 100644 index 0000000..9a08381 --- /dev/null +++ b/src/Scratch/en/validation/index.html @@ -0,0 +1,94 @@ + + + + + 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 + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/files/YAquaBubbles.dmg b/src/Scratch/files/YAquaBubbles.dmg new file mode 100644 index 0000000..67ba05b Binary files /dev/null and b/src/Scratch/files/YAquaBubbles.dmg differ diff --git a/src/Scratch/files/YClock.dmg b/src/Scratch/files/YClock.dmg new file mode 100644 index 0000000..3ca0ac4 Binary files /dev/null and b/src/Scratch/files/YClock.dmg differ diff --git a/src/Scratch/files/YPassword-1.6.zip b/src/Scratch/files/YPassword-1.6.zip new file mode 100644 index 0000000..769002d Binary files /dev/null and b/src/Scratch/files/YPassword-1.6.zip differ diff --git a/src/Scratch/files/YPassword-1.7.zip b/src/Scratch/files/YPassword-1.7.zip new file mode 100644 index 0000000..ad3039e Binary files /dev/null and b/src/Scratch/files/YPassword-1.7.zip differ diff --git a/src/Scratch/files/YPassword-1.8.zip b/src/Scratch/files/YPassword-1.8.zip new file mode 100644 index 0000000..097e8dd Binary files /dev/null and b/src/Scratch/files/YPassword-1.8.zip differ diff --git a/src/Scratch/files/cv.pdf b/src/Scratch/files/cv.pdf new file mode 100644 index 0000000..8a789aa Binary files /dev/null and b/src/Scratch/files/cv.pdf differ diff --git a/src/Scratch/files/cv_en.pdf b/src/Scratch/files/cv_en.pdf new file mode 100644 index 0000000..8a789aa Binary files /dev/null and b/src/Scratch/files/cv_en.pdf differ diff --git a/src/Scratch/files/forcePaste.app.zip b/src/Scratch/files/forcePaste.app.zip new file mode 100644 index 0000000..8cd3cc3 Binary files /dev/null and b/src/Scratch/files/forcePaste.app.zip differ diff --git a/src/Scratch/files/resume/beamerthemesolarized-dark.sty b/src/Scratch/files/resume/beamerthemesolarized-dark.sty new file mode 100644 index 0000000..a8ce42a --- /dev/null +++ b/src/Scratch/files/resume/beamerthemesolarized-dark.sty @@ -0,0 +1,156 @@ +% Beamer Color Theme using the Solarized Palette, +% http://ethanschoonover.com/solarized. +% +% Copyright 2012 Jeffrey B. Arnold +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . + +\ProvidesPackage{beamercolorthemesolarized}[2013/10/11 1.0.1 Solarized color theme for beamer] +\RequirePackage{etoolbox} +\RequirePackage{kvoptions} + +%% This is ugly. First time using options and conditionals in LaTeX +\SetupKeyvalOptions{ + family=solarized, + prefix=solarized@, +} +\DeclareBoolOption[false]{dark} +\DeclareComplementaryOption{light}{dark} +\DeclareStringOption[yellow]{accent}[yellow] +\ProcessKeyvalOptions* + +% Solarized palette +\definecolor{solarizedBase3}{HTML}{002B36} +\definecolor{solarizedBase2}{HTML}{073642} +\definecolor{solarizedBase1}{HTML}{586e75} +\definecolor{solarizedBase0}{HTML}{657b83} +\definecolor{solarizedBase00}{HTML}{839496} +\definecolor{solarizedBase01}{HTML}{93a1a1} +\definecolor{solarizedBase02}{HTML}{EEE8D5} +\definecolor{solarizedBase03}{HTML}{FDF6E3} +\definecolor{solarizedYellow}{HTML}{B58900} +\definecolor{solarizedOrange}{HTML}{CB4B16} +\definecolor{solarizedRed}{HTML}{DC322F} +\definecolor{solarizedMagenta}{HTML}{D33682} +\definecolor{solarizedViolet}{HTML}{6C71C4} +\definecolor{solarizedBlue}{HTML}{268BD2} +\definecolor{solarizedCyan}{HTML}{2AA198} +\definecolor{solarizedGreen}{HTML}{859900} + +% Set Accent color +% Ugly. Should be done with a switch +\ifdefstring{\solarized@accent}{yellow}{ + \colorlet{solarizedAccent}{solarizedYellow} +}{} +\ifdefstring{\solarized@accent}{orange}{ + \colorlet{solarizedAccent}{solarizedOrange} +}{} +\ifdefstring{\solarized@accent}{red}{ + \colorlet{solarizedAccent}{solarizedRed} +}{} +\ifdefstring{\solarized@accent}{magenta}{ + \colorlet{solarizedAccent}{solarizedMagenta} +}{} +\ifdefstring{\solarized@accent}{violet}{ + \colorlet{solarizedAccent}{solarizedViolet} +}{} +\ifdefstring{\solarized@accent}{blue}{ + \colorlet{solarizedAccent}{solarizedBlue} +}{} +\ifdefstring{\solarized@accent}{cyan}{ + \colorlet{solarizedAccent}{solarizedCyan} +}{} +\ifdefstring{\solarized@accent}{green}{ + \colorlet{solarizedAccent}{solarizedGreen} +}{} + +%% Set base colors for dark or light versions +%% Dark +% Switch between light and dark themes using the method in the CSS +% stylesheet http://ethanschoonover.com/solarized +\ifboolexpe{ bool {solarized@dark}}{ + \colorlet{solarizedRebase03}{solarizedBase03} + \colorlet{solarizedRebase02}{solarizedBase02} + \colorlet{solarizedRebase01}{solarizedBase01} + \colorlet{solarizedRebase00}{solarizedBase00} + \colorlet{solarizedRebase0}{solarizedBase0} + \colorlet{solarizedRebase1}{solarizedBase1} + \colorlet{solarizedRebase2}{solarizedBase2} + \colorlet{solarizedRebase3}{solarizedBase3} +}{ + %% Light + \colorlet{solarizedRebase03}{solarizedBase3} + \colorlet{solarizedRebase02}{solarizedBase2} + \colorlet{solarizedRebase01}{solarizedBase1} + \colorlet{solarizedRebase00}{solarizedBase0} + \colorlet{solarizedRebase0}{solarizedBase00} + \colorlet{solarizedRebase1}{solarizedBase01} + \colorlet{solarizedRebase2}{solarizedBase02} + \colorlet{solarizedRebase3}{solarizedBase03} +} + +\mode + +\setbeamercolor{normal text}{fg=solarizedRebase0, bg=solarizedRebase03} +\setbeamercolor{alerted text}{fg=solarizedAccent} +% based css pre element +\setbeamercolor{example text}{fg=solarizedRebase1, bg=solarizedRebase02} + +% Header and footer from CSS +\setbeamercolor{footline}{bg=solarizedRebase02,fg=solarizedRebase01} +\setbeamercolor{headline}{bg=solarizedRebase01,fg=solarizedRebase1} + +% Titles +\setbeamercolor*{titlelike}{fg=solarizedAccent} +\setbeamercolor*{frametitle}{fg=solarizedAccent} +\setbeamercolor*{title}{fg=solarizedAccent} + +% Structure elements use css style for header +\setbeamercolor*{structure}{bg=solarizedRebase01, fg=solarizedRebase1} + +% Do not mess with subtle colors in palette. I don't like it. +\setbeamercolor*{palette primary}{bg=solarizedRebase01, fg=solarizedRebase1} +\setbeamercolor*{palette secondary}{bg=solarizedRebase01, fg=solarizedRebase1} +\setbeamercolor*{palette tertiary}{bg=solarizedRebase01, fg=solarizedRebase1} +\setbeamercolor*{palette quaternary}{bg=solarizedRebase01, fg=solarizedRebase1} + +% Make Blocks slightly lighter/darker +\setbeamercolor{block title}{fg=solarizedAccent, bg=solarizedRebase02} +%\setbeamercolor{block title alerted}{} +%\setbeamercolor{block title example}{} + +\setbeamercolor{block body}{parent=normal text, bg=solarizedRebase02} +% \setbeamercolor{block body alerted}{} +% \setbeamercolor{block body example}{} + +% same as footline +% Set Sidebar and footline to use the css style for footer +\setbeamercolor*{sidebar}{parent=headline} +\setbeamercolor*{palette sidebar primary}{fg=solarizedRebase01, fg=solarizedRebase1} +\setbeamercolor*{palette sidebar secondary}{fg=solarizedRebase01, fg=solarizedRebase1} +\setbeamercolor*{palette sidebar tertiary}{fg=solarizedRebase01, fg=solarizedRebase1} +\setbeamercolor*{palette sidebar quaternary}{fg=solarizedRebase01, fg=solarizedRebase1} + +% border-color for headings +\setbeamercolor{separation line}{fg=solarizedRebase0} +\setbeamercolor{fine separation line}{fg=solarizedRebase0} + +\setbeamercolor*{section in sidebar shaded}{parent=palette sidebar primary} +% a.hover.navlink in CSS +\setbeamercolor*{section in sidebar}{parent=palette sidebar primary, fg=solarizedRebase02} +\setbeamercolor*{subsection in sidebar}{parent=section in sidebar} +\setbeamercolor*{subsection in sidebar shaded}{parent=section in sidebar shaded} + +\mode + diff --git a/src/Scratch/files/resume/beamerthemesolarized.sty b/src/Scratch/files/resume/beamerthemesolarized.sty new file mode 100644 index 0000000..9c21d34 --- /dev/null +++ b/src/Scratch/files/resume/beamerthemesolarized.sty @@ -0,0 +1,156 @@ +% Beamer Color Theme using the Solarized Palette, +% http://ethanschoonover.com/solarized. +% +% Copyright 2012 Jeffrey B. Arnold +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . + +\ProvidesPackage{beamercolorthemesolarized}[2013/10/11 1.0.1 Solarized color theme for beamer] +\RequirePackage{etoolbox} +\RequirePackage{kvoptions} + +%% This is ugly. First time using options and conditionals in LaTeX +\SetupKeyvalOptions{ + family=solarized, + prefix=solarized@, +} +\DeclareBoolOption[false]{dark} +\DeclareComplementaryOption{light}{dark} +\DeclareStringOption[yellow]{accent}[yellow] +\ProcessKeyvalOptions* + +% Solarized palette +\definecolor{solarizedBase03}{HTML}{002B36} +\definecolor{solarizedBase02}{HTML}{073642} +\definecolor{solarizedBase01}{HTML}{586e75} +\definecolor{solarizedBase00}{HTML}{657b83} +\definecolor{solarizedBase0}{HTML}{839496} +\definecolor{solarizedBase1}{HTML}{93a1a1} +\definecolor{solarizedBase2}{HTML}{EEE8D5} +\definecolor{solarizedBase3}{HTML}{FDF6E3} +\definecolor{solarizedYellow}{HTML}{B58900} +\definecolor{solarizedOrange}{HTML}{CB4B16} +\definecolor{solarizedRed}{HTML}{DC322F} +\definecolor{solarizedMagenta}{HTML}{D33682} +\definecolor{solarizedViolet}{HTML}{6C71C4} +\definecolor{solarizedBlue}{HTML}{268BD2} +\definecolor{solarizedCyan}{HTML}{2AA198} +\definecolor{solarizedGreen}{HTML}{859900} + +% Set Accent color +% Ugly. Should be done with a switch +\ifdefstring{\solarized@accent}{yellow}{ + \colorlet{solarizedAccent}{solarizedYellow} +}{} +\ifdefstring{\solarized@accent}{orange}{ + \colorlet{solarizedAccent}{solarizedOrange} +}{} +\ifdefstring{\solarized@accent}{red}{ + \colorlet{solarizedAccent}{solarizedRed} +}{} +\ifdefstring{\solarized@accent}{magenta}{ + \colorlet{solarizedAccent}{solarizedMagenta} +}{} +\ifdefstring{\solarized@accent}{violet}{ + \colorlet{solarizedAccent}{solarizedViolet} +}{} +\ifdefstring{\solarized@accent}{blue}{ + \colorlet{solarizedAccent}{solarizedBlue} +}{} +\ifdefstring{\solarized@accent}{cyan}{ + \colorlet{solarizedAccent}{solarizedCyan} +}{} +\ifdefstring{\solarized@accent}{green}{ + \colorlet{solarizedAccent}{solarizedGreen} +}{} + +%% Set base colors for dark or light versions +%% Dark +% Switch between light and dark themes using the method in the CSS +% stylesheet http://ethanschoonover.com/solarized +\ifboolexpe{ bool {solarized@dark}}{ + \colorlet{solarizedRebase03}{solarizedBase03} + \colorlet{solarizedRebase02}{solarizedBase02} + \colorlet{solarizedRebase01}{solarizedBase01} + \colorlet{solarizedRebase00}{solarizedBase00} + \colorlet{solarizedRebase0}{solarizedBase0} + \colorlet{solarizedRebase1}{solarizedBase1} + \colorlet{solarizedRebase2}{solarizedBase2} + \colorlet{solarizedRebase3}{solarizedBase3} +}{ + %% Light + \colorlet{solarizedRebase03}{solarizedBase3} + \colorlet{solarizedRebase02}{solarizedBase2} + \colorlet{solarizedRebase01}{solarizedBase1} + \colorlet{solarizedRebase00}{solarizedBase0} + \colorlet{solarizedRebase0}{solarizedBase00} + \colorlet{solarizedRebase1}{solarizedBase01} + \colorlet{solarizedRebase2}{solarizedBase02} + \colorlet{solarizedRebase3}{solarizedBase03} +} + +\mode + +\setbeamercolor{normal text}{fg=solarizedRebase0, bg=solarizedRebase03} +\setbeamercolor{alerted text}{fg=solarizedAccent} +% based css pre element +\setbeamercolor{example text}{fg=solarizedRebase1, bg=solarizedRebase02} + +% Header and footer from CSS +\setbeamercolor{footline}{bg=solarizedRebase02,fg=solarizedRebase01} +\setbeamercolor{headline}{bg=solarizedRebase01,fg=solarizedRebase1} + +% Titles +\setbeamercolor*{titlelike}{fg=solarizedAccent} +\setbeamercolor*{frametitle}{fg=solarizedAccent} +\setbeamercolor*{title}{fg=solarizedAccent} + +% Structure elements use css style for header +\setbeamercolor*{structure}{bg=solarizedRebase01, fg=solarizedRebase1} + +% Do not mess with subtle colors in palette. I don't like it. +\setbeamercolor*{palette primary}{bg=solarizedRebase01, fg=solarizedRebase1} +\setbeamercolor*{palette secondary}{bg=solarizedRebase01, fg=solarizedRebase1} +\setbeamercolor*{palette tertiary}{bg=solarizedRebase01, fg=solarizedRebase1} +\setbeamercolor*{palette quaternary}{bg=solarizedRebase01, fg=solarizedRebase1} + +% Make Blocks slightly lighter/darker +\setbeamercolor{block title}{fg=solarizedAccent, bg=solarizedRebase02} +%\setbeamercolor{block title alerted}{} +%\setbeamercolor{block title example}{} + +\setbeamercolor{block body}{parent=normal text, bg=solarizedRebase02} +% \setbeamercolor{block body alerted}{} +% \setbeamercolor{block body example}{} + +% same as footline +% Set Sidebar and footline to use the css style for footer +\setbeamercolor*{sidebar}{parent=headline} +\setbeamercolor*{palette sidebar primary}{fg=solarizedRebase01, fg=solarizedRebase1} +\setbeamercolor*{palette sidebar secondary}{fg=solarizedRebase01, fg=solarizedRebase1} +\setbeamercolor*{palette sidebar tertiary}{fg=solarizedRebase01, fg=solarizedRebase1} +\setbeamercolor*{palette sidebar quaternary}{fg=solarizedRebase01, fg=solarizedRebase1} + +% border-color for headings +\setbeamercolor{separation line}{fg=solarizedRebase0} +\setbeamercolor{fine separation line}{fg=solarizedRebase0} + +\setbeamercolor*{section in sidebar shaded}{parent=palette sidebar primary} +% a.hover.navlink in CSS +\setbeamercolor*{section in sidebar}{parent=palette sidebar primary, fg=solarizedRebase02} +\setbeamercolor*{subsection in sidebar}{parent=section in sidebar} +\setbeamercolor*{subsection in sidebar shaded}{parent=section in sidebar shaded} + +\mode + diff --git a/src/Scratch/files/resume/resume.beamer.pdf b/src/Scratch/files/resume/resume.beamer.pdf new file mode 100644 index 0000000..fb85b05 Binary files /dev/null and b/src/Scratch/files/resume/resume.beamer.pdf differ diff --git a/src/Scratch/files/resume/resume.html b/src/Scratch/files/resume/resume.html new file mode 100644 index 0000000..8bf9ca4 --- /dev/null +++ b/src/Scratch/files/resume/resume.html @@ -0,0 +1,607 @@ + + + + + + + + + Resume + + + + + +
+

Resume

+

Yann Esposito

+

26 July 2016

+
+ + +

Yann Esposito

+

+

PDF Version

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
nameYann Esposito
mail
port(+33)650845271
addressBât 9, Résidence Saint Marc
591, avenue Jean Aicard
06700, Saint Laurent du Var
+

Professional Background

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
2016Clojure Software Engineer for Cisco (Threatgrid), Remote
20132016Machine Learning Scientist & Software Engineer at Vigiglobe, Sophia Antipolis, France
2010Co-Founder of GridPocket, Sophia Antipolis, France
20072013AirFrance, Sophia Antipolis, France
10/20063/2007Post Ph.D., Hubert Curien Laboratory, St-Etienne, France
10/20049/2006ATER (College Degree Teach & Research), Marseille, France
10/20019/2004University Monitor (College Degree Teach & Research), Marseille, France
19952000Miscellaneous summer jobs
+

Education

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
2004CS Ph.D. in Machine Learning at Université de Provence
2001D.E.A. (Equivalent to Master in Computer science)
2000Maîtrise in Computer Science
1999Licence in Computer Science
1998DEUG MIAS (Math)
1995BAC S (Math)
+

Research Activies: Publications

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
International Journal[Fundamenta Informaticæ, 2008]
[Pattern Recognition, 2004]
Internation Conferences[ECML 2008] [ICGI 2006] [COLT 2006]
[COLT 2004] [ICALP 2003] [ICGI 2002]
National Journal[JEDAI 2002]
National Conferences[CAp’06] [CAp’04] [CAp’03]
+ +

Presentation

+

I am French with a Post Ph.D in Machine Learning1. Furthermore I love web programming and design.

+

I am currently working remotely for Cisco Security team as a Clojure Software Engineer.

+

Previously I worked for Vigiglobe. The first six months I worked with node.js (API/MongoDB/Web). Then we upgraded our stack to Clojure, Haskell, Mesos, Kafka, Druid, etc… At that time we were two to make all technical decisions. In the end we made a real time analytics of social media content on a scalable architecture. Actually our architecture is able to manage (Aggregation & Machine Learning) thousands of messages per second.2 In particular, I’ve written an Haskell twitter stream absorber able to handle thousands of tweets per seconds. And I coded myself a real time sentiment analysis module taking algebraic properties into account to optimize its efficiency.

+
    +
  • During my Ph.D. I made a C++ program (github3 and resume4). I coded most of standard HMM learning algorithms. I developed an algorithm which I invented during my Ph.D. which use some operational optimization algorithm. During this period I published articles in international conferences and I taught Computer Science to college students.

  • +
  • At the Hubert Curien Laboratory I made my post Ph.D. I developed a scientific application in Java/applet/JWS that should be used by biologists. The code has been updated a bit since my 6 month post Ph.D5.

  • +
  • I worked in the web industry for Airfrance. My work environment was quite heterogeneous. From shell scripting to manage huge amount of data, web design and production environment.

  • +
  • I worked for GridPocket (I am a co-founder). This is a French startup specialized in Electric Grid. I created a private6 web application.

  • +
  • I’ve also written an iOS application to manage passwords7.

  • +
  • I am the author of some quite popular blog posts8.

  • +
+

For an almost exhaustive list of my projects, you could check my github account: github.com/yogsototh

+ +

Public things done

+
    +
  • Cisco (threatgrid) Security & Threat Management.
  • +
  • Vigiglobe architecture able to analyze thousands of social media messages in realtime. In particular, real time Machine Learning & Statistics.
  • +
  • YPassword iOS application
  • +
  • Gridpocket web services (from conception to realization, works in correlation with a mobile app)
  • +
  • DEES: a 10.000 line C++ command line program. This program implement most HMM standard algorithms & inference algorithms.
  • +
  • SeDiL: a Java application using Swing UI. The goal is to provide biologist an easy way to use an algorithm that generate Similarity Matrices for strings but also for Tree structures. Most graphics was done by me, including the drawing of trees. I didn’t used a library for that purpose.
  • +
  • For YPassword ; a Dashboard Widget, a web interface, a command line tool.
  • +
  • Some websites: yannesposito.com, ypassword.espozito.com
  • +
  • Written a thesis in Machine Learning and published in major international conferences: [ICALP 2003], [COLT 2004] & [COLT 2006].
  • +
  • A full javascript web application which display Electric consumption in real time.
  • +
  • krambook (the engine I use to create this document. I exported it in HTML, PDF (using ) and SVG.
  • +
  • some Mac OS X screensaver, a MetaPost plugin to draw Automata, an RFC-like document to help my student to make a TOR like network, etc…
  • +
  • a bunch of other projects see http://github.com/yogsototh
  • +
+ +

Technical Competences

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LanguagesHaskell, Clojure, Javascript,
scheme, C, camL, C++, Ruby, Perl, Java, Python, PHP
Web frontendelm, Clojurescript, Reagent, Angular.js, sass, etc…
Web frameworkscompojure-api, Yesod, servant, actionhero
ML Toolsweka, SVMlight
Stream Computingkafka, druid, storm (with clojure)
UNIXShell scripts (zsh, bash), awk, , ConTeXt, metapost
VCSgit, Bazaar (DCVS), subversion (svn), CVS
Mac/iOSObjective-C Cocoa (Mac & iOS), Dahsboard widget,
Quartz Composer
+ +

Jobs

+

Clojure Software Engineer for Cisco 2016 →

+
    +
  • Remote
  • +
+ + + + + + + + + + + +
ProductSecurity Threat Management
RoleClojure Software Engineer
+

Machine Learning Scientist & Software Engineer for Vigiglobe 2013 → 2016

+
    +
  • Sophia Antipolis, France
  • +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ProductScalable Real Time Social Media Analytics
Sentiment Analysis
Many client side web applications (Angular.js & reagent)
RoleMachine Learning Scientist
(fast sentiment analysis, learning protocols, etc..)
Full stack engineer (backend to frontend architecture)
KeywordsClojure, Haskell, node.js, reagent, Angular.js, Stream computing
+

Co-Founder & freelance for GridPocket 2010 →

+

Sophia Antipolis, France

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ProductTwo API server (one for client, another for administration)
A private client side web application
An iPhone Application
Some Linux boxes to send data to the servers
A Linux driver
RoleFull technical responsibilities
KeywordsRuby, REST, JSON, HTML, CSS, Javascript, AJAX,
jQuery, Objective-C, ASIHTTPRequest, CorePlot, CoreData, C
+

Consultant, AirFrance 2007 →

+

Sophia Antipolis, France

+ + + + + + + + + + + + + + + +
RoleIn charge of the Airfrance CMS for their website.
KeywordsTeamSite, Perl, XML, XHTML, CSS, javascript, JSP,
Unix (Solaris/Linux), Bazaar
+

Post Ph.D 10/2006 → 3/2007

+

Université Jean Monet, Laboratoire Hubert Curien, Saint-Etienne

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
ProductSeDiL
RoleJava Developer
ResearchSimilarity measure between strings or XML trees
Contact
KeywordsUML, Java 1.5, Swing, Java 2D, Java Web Start, Applet,
subversion, XML, XHTML, PHP
+

Details:

+
+

Java application: 11 000 lines with javadoc

+

Main functionalities

+
    +
  • learn edit matrices
  • +
  • compute edit distances between trees or strings
  • +
  • visualize trees or sequences (JAVA 2D)
  • +
  • classification using K means
  • +
  • Generate random tree couple from an edit distance matrice
  • +
+

Web: http://labh-curien.univ-st-etienne.fr/SEDiL/

+
+

ATER 10/2004 → 9/2006

+

Research & Teacher, Université de Provence, Marseille

+

teach 1/2, research 1/6, C++ development 1/3

+

DEES ; a C++ software

+
+

7500 lines of C++ code, 10.000 with comments

+

Main functionalities:

+
    +
  • Mulitiplicity Automata, HMM & PDA Inference,
  • +
  • Baum Welch & Viterbi Algorithms,
  • +
  • GraphViz export,
  • +
  • String Generation from many Models,
  • +
+ + + + + + + + + + + + + + + +
LanguagesC++
APISTL
EnvironmentLinux (Debian) & Windows XP
+
+

Moniteur des Universités 10/2001 → 9/2004

+

Université de Provence, Marseille

+

teach 1/3, research 1/3, C++ Development 1/3

+

Creation of DEES (see preceeding entry).

+ +

Diploma

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
2004Ph.D. degree in Machine Learning
2001D.E.A. in Computer Science (equivalent to master)
2000Maîtrise d’Informatique
1999Licence in Computer Science
1998DEUG MIAS (math)
1995BAC S (math)
+ +

Scientific Publications

+

International

+ ++++ + + + + + + + + + + + + + + +
Journals[Fundamenta Inforamticæ vol.86 2008][Pattern Recognition, 2004]
Conferences[ECML 2008] [COLT 2006] [ICGI 2006][COLT 2004] [ICALP 2003] [ICGI 2002]
Workshop[TAGI05]
+

National (French)

+ + + + + + + + + + + + + + + +
Journals[JEDAI, 2003]
Conferences[CAP 2006] [CAP 2004] [CAP 2003]
Thesis[Université de Provence 2004]
+ +

Projects

+

Most of my latest programming activities are publicly available at github.com/yogsototh

+

Haskell libraries

+
    +
  • Link to list of packages

  • +
  • holy-project
  • +
  • human-readable-duration
  • +
  • wai-middleware-caching-lru
  • +
  • wai-middleware-caching-redis
  • +
  • wai-middleware-caching

  • +
+

YPassword 2008 →

+

Mainly an iOS application:

+ +

I’ve done fully the website from scratch. Also there are some javascript implementation of YPassword method:

+
+
    +
  • a Mac OS X dashboard widget,
  • +
  • a Cappuccino Web application,
  • +
  • a jQuery Web application,
  • +
  • a command line tool,
  • +
  • an Applescript helper
  • +
+
+

Anonymous Network Project 02/2006 → 06/2006

+

Made a protocol similar to TOR for student.

+

Other projects

+
    +
  • Web Application used for private team usage at AirFrance 2008 → This application is just done for teh lulz. Not related to the Airfrance work. But still pleasant. Javascript(Prototype.js, Scriptaculous), CSS, PHP/MySQL, Google Talk
  • +
  • metapost package to draw Automata 2003 → 2004 metapost
  • +
  • Mac OS X Screensavers (YClock & YAquaBubbles) 2003 → 2004 Objective-C,Quartz Composer,Cocoa
  • +
+

You could find even more information by looking at:

+ +
+
+
    +
  1. To be more precise in Grammatical Inference

  2. +
  3. http://vigiglobe.com

  4. +
  5. https://github.com/yogsototh/DEES

  6. +
  7. http://yann.esposito.free.fr/dees.php?lang=en

  8. +
  9. http://labh-curien.univ-st-etienne.fr/SEDiL/faq.php?lang=en (I like to believe I became a better designer ☺)

  10. +
  11. Sorry the code is private I can’t show it :(.

  12. +
  13. http://ypassword.espozito.com

  14. +
  15. http://yannesposito.com/Scratch/en/blog/

  16. +
+
+ + + diff --git a/src/Scratch/files/resume/resume.md b/src/Scratch/files/resume/resume.md new file mode 100644 index 0000000..23636ab --- /dev/null +++ b/src/Scratch/files/resume/resume.md @@ -0,0 +1,361 @@ +--- +title: Resume +author: Yann Esposito +abstract: Yann Esposito's Resume +theme: brutalist +highlight-style: solarized-dark +date: 26 July 2016 +--- + + +\newpage + +# Yann Esposito + +\iffalse + +[PDF Version](./resume.pdf) + +\fi + +-------- --------------------------------------------------------------- +name Yann Esposito +mail +port (+33)650845271 +address Bât 9, Résidence Saint Marc + 591, avenue Jean Aicard + 06700, Saint Laurent du Var +-------- --------------------------------------------------------------- + +## Professional Background + +--------- ------ -------- ----------------------------------------------------------------------------------------- + _2016_ → Clojure Software Engineer for Cisco (Threatgrid), _Remote_ + + _2013_ → _2016_ Machine Learning Scientist & Software Engineer at Vigiglobe, + _Sophia Antipolis, France_ + + _2010_ → Co-Founder of GridPocket, _Sophia Antipolis, France_ + + _2007_ → _2013_ AirFrance, _Sophia Antipolis, France_ + +_10/2006_ → _3/2007_ Post Ph.D., Hubert Curien Laboratory, _St-Etienne, France_ + +_10/2004_ → _9/2006_ ATER (College Degree Teach _&_ Research), _Marseille, France_ + +_10/2001_ → _9/2004_ University Monitor (College Degree Teach _&_ Research), + _Marseille, France_ + + _1995_ → _2000_ Miscellaneous summer jobs +--------- ------ -------- ----------------------------------------------------------------------------------------- + +## Education + +----- --------------------------------------------- +_2004_ CS Ph.D. in Machine Learning at Université de Provence +_2001_ D.E.A. (Equivalent to Master in Computer science) +_2000_ Maîtrise in Computer Science +_1999_ Licence in Computer Science +_1998_ DEUG MIAS (Math) +_1995_ BAC S (Math) +----- --------------------------------------------- + +## Research Activies: Publications + +-------------------------- --------------------------------------------- +_International Journal_ [Fundamenta Informaticæ, 2008] + [Pattern Recognition, 2004] +_Internation Conferences_ [ECML 2008] [ICGI 2006] [COLT 2006] + [COLT 2004] [ICALP 2003] [ICGI 2002] +_National Journal_ [JEDAI 2002] +_National Conferences_ [CAp'06] [CAp'04] [CAp'03] +-------------------------- --------------------------------------------- + +\newpage + +# Presentation + +I am French with a Post Ph.D in Machine Learning[^10]. +Furthermore I love web programming and design. + +I am currently working remotely for Cisco Security team +as a Clojure Software Engineer. + +Previously I worked for Vigiglobe. +The first six months I worked with `node.js` (API/MongoDB/Web). +Then we upgraded our stack to _Clojure, Haskell, Mesos, Kafka, Druid_, etc... +At that time we were two to make all technical decisions. +In the end we made a real time analytics of social media content on a scalable architecture. +Actually our architecture is able to manage (Aggregation & Machine Learning) thousands of messages per second.[^7] +In particular, I've written an Haskell twitter stream absorber able to handle thousands of tweets per seconds. And I coded myself a real time sentiment analysis module taking algebraic properties into account to optimize its efficiency. + +- During my Ph.D. I made a C++ program (github[^1] and resume[^2]). + I coded most of standard HMM learning algorithms. + I developed an algorithm which I invented during my Ph.D. which use some operational optimization algorithm. + During this period I published articles in international conferences and + I taught Computer Science to college students. + +- At the Hubert Curien Laboratory I made my post Ph.D. + I developed a scientific application in Java/applet/JWS that should be used by biologists. + The code has been updated a bit since my 6 month post Ph.D[^3]. + +- I worked in the web industry for Airfrance. + My work environment was quite heterogeneous. + From shell scripting to manage huge amount of data, web design + and production environment. + +- I worked for GridPocket (I am a co-founder). + This is a French startup specialized in Electric Grid. + I created a private[^6] web application. + +- I've also written an iOS application to manage passwords[^4]. + +- I am the author of some quite popular blog posts[^5]. + +For an almost exhaustive list of my projects, you could check my +github account: [github.com/yogsototh](https://github.com/yogsototh) + +[^10]: To be more precise in Grammatical Inference +[^1]: +[^2]: +[^3]: (I like to believe I became a better designer ☺) +[^4]: +[^5]: +[^6]: Sorry the code is private I can't show it :(. +[^7]: + +\newpage + +# Public things done + +- [Cisco (threatgrid)](http://cisco.com) Security & Threat Management. +- [Vigiglobe](http://vigiglobe.com) architecture able to analyze thousands of social media messages in realtime. In particular, real time Machine Learning & Statistics. +- [Gridpocket](http://gridpocket.com) web services (from conception to realization, works in correlation with a mobile app) +- [DEES](https://github.com/yogsototh/DEES): a 10.000 line C++ command line program. This program implement most [HMM](http://en.wikipedia.org/wiki/Hidden_Markov_model) standard algorithms _&_ inference algorithms. +- [SeDiL](http://labh-curien.univ-st-etienne.fr/SEDiL/): a Java application using Swing UI. The goal is to provide biologist an easy way to use an algorithm that generate Similarity Matrices for strings but also for Tree structures. Most graphics was done by me, including the drawing of trees. I didn't used a library for that purpose. +- YPassword iOS application +- [YPassword](http://yannesposito/YPassword) web interface in elm +- Also relative to YPassword ; a Dashboard Widget, a command line tool. +- Some websites: [yannesposito.com](http://yannesposito.com) +- Written a thesis in Machine Learning and published in major international conferences: + [ICALP 2003], [COLT 2004] _&_ [COLT 2006]. +- A full javascript web application which display Electric consumption in real time. +- [mkdocs](http://yogsototh.github.io/mkdocs) (the engine I use to create this document. I exported it in HTML, PDF (using \LaTeX) and SVG. +- some Mac OS X screensaver, a MetaPost plugin to draw Automata, an RFC-like document to help my student to make a TOR like network, etc... +- a bunch of other projects see [http://github.com/yogsototh](http://github.com/yogsototh) + +\newpage + +# Technical Competences + +------------------ ----------------------------------------------------------------- +Languages __Haskell__, __Clojure__, __Javascript__, + scheme, C, camL, C++, Ruby, Perl, Java, Python, PHP +Web frontend __elm__, __Clojurescript__, __Reagent__, __Angular.js__, __sass__, etc... +Web frameworks __compojure-api__, __Yesod__, __servant__, actionhero +ML Tools __weka__, SVMlight +Stream Computing __kafka__, __druid__, storm (with clojure) +UNIX Shell scripts (zsh, bash), awk, \LaTeX, ConTeXt, metapost +VCS __git__, Bazaar (DCVS), subversion (svn), CVS +Mac/iOS __Objective-C Cocoa (Mac & iOS)__, Dahsboard widget, + Quartz Composer +------------------ ----------------------------------------------------------------- + +\newpage + +# Jobs + +## Clojure Software Engineer for Cisco _2016 →_ + +- _Remote_ + +------- -------------------------- +Product Security Threat Management +Role Clojure Software Engineer +------- -------------------------- + +## Machine Learning Scientist _&_ Software Engineer for Vigiglobe _2013 → 2016_ + +- _Sophia Antipolis, France_ + +---------- ------------------------------------------------- +Product Scalable Real Time Social Media Analytics + Sentiment Analysis + Many client side web applications (Angular.js & reagent) +Role Machine Learning Scientist + (fast sentiment analysis, learning protocols, etc..) + Full stack engineer (backend to frontend architecture) +Keywords Clojure, Haskell, node.js, reagent, Angular.js, Stream computing +---------- ------------------------------------------------- + +## Co-Founder _&_ freelance for GridPocket _2010 →_ + +_Sophia Antipolis, France_ + +---------- ------------------------------------------------- +Product Two API server (one for client, another for administration) + A private client side web application + An iPhone Application + Some Linux boxes to send data to the servers + A Linux driver +Role Full technical responsibilities +Keywords Ruby, REST, JSON, HTML, CSS, Javascript, AJAX, + jQuery, Objective-C, ASIHTTPRequest, CorePlot, CoreData, C +---------- ------------------------------------------------- + +## Consultant, AirFrance _2007 →_ + +_Sophia Antipolis, France_ + +---------- ------------------------------------------------- +Role In charge of the Airfrance CMS for their website. +Keywords TeamSite, Perl, XML, XHTML, CSS, javascript, JSP, + Unix (Solaris/Linux), Bazaar +---------- ------------------------------------------------- + +## Post Ph.D _10/2006 → 3/2007_ + +_Université Jean Monet, Laboratoire Hubert Curien, Saint-Etienne_ + + +---------- ------------------------------------------------- +Product [SeDiL](http://labh-curien.univ-st-etienne.fr/SEDiL/) +Role Java Developer +Research Similarity measure between strings or XML trees +Contact [Marc Sebban](mailto://marc.sebban@univ-st-etienne.fr) +Keywords UML, Java 1.5, Swing, Java 2D, Java Web Start, Applet, + subversion, XML, XHTML, PHP +---------- ------------------------------------------------- + +Details: + +> Java application: _11 000 lines with javadoc_ +> +> Main functionalities +> +> - learn edit matrices +> - compute edit distances between trees or strings +> - visualize trees or sequences (JAVA 2D) +> - classification using K means +> - Generate random tree couple from an edit distance matrice +> +> Web: [http://labh-curien.univ-st-etienne.fr/SEDiL/](http://labh-curien.univ-st-etienne.fr/SEDiL/) + + +## ATER _10/2004 → 9/2006_ + +Research _&_ Teacher, Université de Provence, Marseille + +_teach 1/2, research 1/6, C++ development 1/3_ + +DEES ; a C++ software + +> _7500 lines of C++ code, 10.000 with comments_ +> +> Main functionalities: +> +> - Mulitiplicity Automata, HMM _&_ PDA Inference, +> - Baum Welch _&_ Viterbi Algorithms, +> - GraphViz export, +> - String Generation from many Models, +> +> ------------ ------------------------------ +> Languages C++ +> API STL +> Environment Linux (Debian) _&_ Windows XP +> ------------ ------------------------------ + +## Moniteur des Universités _10/2001 → 9/2004_ + +Université de Provence, Marseille + +_teach 1/3, research 1/3, C++ Development 1/3_ + +Creation of DEES (see preceeding entry). + +\newpage + +# Diploma + +------ ----------------------------------------------------- +_2004_ Ph.D. degree in Machine Learning +_2001_ D.E.A. in Computer Science (equivalent to master) +_2000_ Maîtrise d’Informatique +_1999_ Licence in Computer Science +_1998_ DEUG MIAS (math) +_1995_ BAC S (math) +------ ----------------------------------------------------- + +\newpage + +# Scientific Publications + +## International + +-------------- ---------------------------------------------------- +Journals [Fundamenta Inforamticæ vol.86 2008] + [Pattern Recognition, 2004] + +Conferences [ECML 2008] [COLT 2006] [ICGI 2006] + [COLT 2004] [ICALP 2003] [ICGI 2002] + +Workshop [TAGI05] +-------------- ---------------------------------------------------- + +## National (French) + +-------------- ---------------------------------------------------- +Journals [JEDAI, 2003] +Conferences [CAP 2006] [CAP 2004] [CAP 2003] +Thesis [Université de Provence 2004] +-------------- ---------------------------------------------------- + +\newpage + +# Projects + +Most of my latest programming activities are publicly available at [github.com/yogsototh](http://github.com/yogsototh) + +## Haskell libraries + +- [Link to list of packages](http://hackage.haskell.org/user/yogsototh) + +- `holy-project` +- `human-readable-duration` +- `wai-middleware-caching-lru` +- `wai-middleware-caching-redis` +- `wai-middleware-caching` + +## YPassword _2008 →_ + +Mainly an iOS application: + +- [YPassword, `http://ypassword.espozito.com`](http://ypassword.espozito.com) + +I've done fully the website from scratch. Also there are some javascript implementation of YPassword method: + +> - a Mac OS X dashboard widget, +> - a Cappuccino Web application, +> - a jQuery Web application, +> - a command line tool, +> - an Applescript helper + +## Anonymous Network Project _02/2006 → 06/2006_ + +Made a protocol similar to [TOR](http://www.torproject.org) for student. + +## Other projects + +- Web Application used for private team usage at AirFrance _2008 →_ + This application is just done _[for teh lulz](http://cache.ohinternet.com/images/thumb/f/fa/4tehlulz.jpg/618px-4tehlulz.jpg)_. + Not related to the Airfrance work. But still pleasant. + _Javascript(Prototype.js, Scriptaculous), CSS, PHP/MySQL, Google Talk_ +- [metapost package](https://github.com/yogsototh/metautomata) to draw Automata _2003 → 2004_ + metapost +- Mac OS X Screensavers ([YClock](https://github.com/yogsototh/YClock) _&_ YAquaBubbles) _2003 → 2004_ + _Objective-C,Quartz Composer,Cocoa_ + +You could find even more information by looking at: + +- My personnal website: [`http://yannesposito.com`](http://yannesposito.com) +- My github account: [`http://github.com/yogsototh`](http://github.com/yogsototh) diff --git a/src/Scratch/files/resume/resume.pdf b/src/Scratch/files/resume/resume.pdf new file mode 100644 index 0000000..a996aa1 Binary files /dev/null and b/src/Scratch/files/resume/resume.pdf differ diff --git a/src/Scratch/files/resume/resume.reveal.html b/src/Scratch/files/resume/resume.reveal.html new file mode 100644 index 0000000..02baa5f --- /dev/null +++ b/src/Scratch/files/resume/resume.reveal.html @@ -0,0 +1,672 @@ + + + + +Resume + + + + + + + + + + + + + + + +
+ + +
+ +
+

Resume

+

Yann Esposito

+

+

26 July 2016

+

+
+ + + +
+

Yann Esposito

+

+

PDF Version

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
nameYann Esposito
mail
port(+33)650845271
addressBât 9, Résidence Saint Marc
591, avenue Jean Aicard
06700, Saint Laurent du Var
+
+

Professional Background

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
2016Clojure Software Engineer for Cisco (Threatgrid), Remote
20132016Machine Learning Scientist & Software Engineer at Vigiglobe,
Sophia Antipolis, France
2010Co-Founder of GridPocket, Sophia Antipolis, France
20072013AirFrance, Sophia Antipolis, France
10/20063/2007Post Ph.D., Hubert Curien Laboratory, St-Etienne, France
10/20049/2006ATER (College Degree Teach & Research), Marseille, France
10/20019/2004University Monitor (College Degree Teach & Research),
Marseille, France
19952000Miscellaneous summer jobs
+
+
+

Education

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
2004CS Ph.D. in Machine Learning at Université de Provence
2001D.E.A. (Equivalent to Master in Computer science)
2000Maîtrise in Computer Science
1999Licence in Computer Science
1998DEUG MIAS (Math)
1995BAC S (Math)
+
+
+

Research Activies: Publications

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
International Journal[Fundamenta Informaticæ, 2008]
[Pattern Recognition, 2004]
Internation Conferences[ECML 2008] [ICGI 2006] [COLT 2006]
[COLT 2004] [ICALP 2003] [ICGI 2002]
National Journal[JEDAI 2002]
National Conferences[CAp'06] [CAp'04] [CAp'03]
+ +
+
+
+

Presentation

+

I am French with a Post Ph.D in Machine Learning1. Furthermore I love web programming and design.

+

I am currently working remotely for Cisco Security team as a Clojure Software Engineer.

+

Previously I worked for Vigiglobe. The first six months I worked with node.js (API/MongoDB/Web). Then we upgraded our stack to Clojure, Haskell, Mesos, Kafka, Druid, etc... At that time we were two to make all technical decisions. In the end we made a real time analytics of social media content on a scalable architecture. Actually our architecture is able to manage (Aggregation & Machine Learning) thousands of messages per second.2 In particular, I've written an Haskell twitter stream absorber able to handle thousands of tweets per seconds. And I coded myself a real time sentiment analysis module taking algebraic properties into account to optimize its efficiency.

+
    +
  • During my Ph.D. I made a C++ program (github3 and resume4). I coded most of standard HMM learning algorithms. I developed an algorithm which I invented during my Ph.D. which use some operational optimization algorithm. During this period I published articles in international conferences and I taught Computer Science to college students.

  • +
  • At the Hubert Curien Laboratory I made my post Ph.D. I developed a scientific application in Java/applet/JWS that should be used by biologists. The code has been updated a bit since my 6 month post Ph.D5.

  • +
  • I worked in the web industry for Airfrance. My work environment was quite heterogeneous. From shell scripting to manage huge amount of data, web design and production environment.

  • +
  • I worked for GridPocket (I am a co-founder). This is a French startup specialized in Electric Grid. I created a private6 web application.

  • +
  • I've also written an iOS application to manage passwords7.

  • +
  • I am the author of some quite popular blog posts8.

  • +
+

For an almost exhaustive list of my projects, you could check my github account: github.com/yogsototh

+ +
+
+

Public things done

+
    +
  • Cisco (threatgrid) Security & Threat Management.
  • +
  • Vigiglobe architecture able to analyze thousands of social media messages in realtime. In particular, real time Machine Learning & Statistics.
  • +
  • YPassword iOS application
  • +
  • Gridpocket web services (from conception to realization, works in correlation with a mobile app)
  • +
  • DEES: a 10.000 line C++ command line program. This program implement most HMM standard algorithms & inference algorithms.
  • +
  • SeDiL: a Java application using Swing UI. The goal is to provide biologist an easy way to use an algorithm that generate Similarity Matrices for strings but also for Tree structures. Most graphics was done by me, including the drawing of trees. I didn't used a library for that purpose.
  • +
  • For YPassword ; a Dashboard Widget, a web interface, a command line tool.
  • +
  • Some websites: yannesposito.com, ypassword.espozito.com
  • +
  • Written a thesis in Machine Learning and published in major international conferences: [ICALP 2003], [COLT 2004] & [COLT 2006].
  • +
  • A full javascript web application which display Electric consumption in real time.
  • +
  • krambook (the engine I use to create this document. I exported it in HTML, PDF (using ) and SVG.
  • +
  • some Mac OS X screensaver, a MetaPost plugin to draw Automata, an RFC-like document to help my student to make a TOR like network, etc...
  • +
  • a bunch of other projects see http://github.com/yogsototh
  • +
+ +
+
+

Technical Competences

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LanguagesHaskell, Clojure, Javascript,
scheme, C, camL, C++, Ruby, Perl, Java, Python, PHP
Web frontendelm, Clojurescript, Reagent, Angular.js, sass, etc...
Web frameworkscompojure-api, Yesod, servant, actionhero
ML Toolsweka, SVMlight
Stream Computingkafka, druid, storm (with clojure)
UNIXShell scripts (zsh, bash), awk, , ConTeXt, metapost
VCSgit, Bazaar (DCVS), subversion (svn), CVS
Mac/iOSObjective-C Cocoa (Mac & iOS), Dahsboard widget,
Quartz Composer
+ +
+
+

Jobs

+
+

Clojure Software Engineer for Cisco 2016 →

+
    +
  • Remote
  • +
+ + + + + + + + + + + +
ProductSecurity Threat Management
RoleClojure Software Engineer
+
+
+

Machine Learning Scientist & Software Engineer for Vigiglobe 2013 → 2016

+
    +
  • Sophia Antipolis, France
  • +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ProductScalable Real Time Social Media Analytics
Sentiment Analysis
Many client side web applications (Angular.js & reagent)
RoleMachine Learning Scientist
(fast sentiment analysis, learning protocols, etc..)
Full stack engineer (backend to frontend architecture)
KeywordsClojure, Haskell, node.js, reagent, Angular.js, Stream computing
+
+
+

Co-Founder & freelance for GridPocket 2010 →

+

Sophia Antipolis, France

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ProductTwo API server (one for client, another for administration)
A private client side web application
An iPhone Application
Some Linux boxes to send data to the servers
A Linux driver
RoleFull technical responsibilities
KeywordsRuby, REST, JSON, HTML, CSS, Javascript, AJAX,
jQuery, Objective-C, ASIHTTPRequest, CorePlot, CoreData, C
+
+
+

Consultant, AirFrance 2007 →

+

Sophia Antipolis, France

+ + + + + + + + + + + + + + + +
RoleIn charge of the Airfrance CMS for their website.
KeywordsTeamSite, Perl, XML, XHTML, CSS, javascript, JSP,
Unix (Solaris/Linux), Bazaar
+
+
+

Post Ph.D 10/2006 → 3/2007

+

Université Jean Monet, Laboratoire Hubert Curien, Saint-Etienne

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
ProductSeDiL
RoleJava Developer
ResearchSimilarity measure between strings or XML trees
Contact
KeywordsUML, Java 1.5, Swing, Java 2D, Java Web Start, Applet,
subversion, XML, XHTML, PHP
+

Details:

+
+

Java application: 11 000 lines with javadoc

+

Main functionalities

+
    +
  • learn edit matrices
  • +
  • compute edit distances between trees or strings
  • +
  • visualize trees or sequences (JAVA 2D)
  • +
  • classification using K means
  • +
  • Generate random tree couple from an edit distance matrice
  • +
+

Web: http://labh-curien.univ-st-etienne.fr/SEDiL/

+
+
+
+

ATER 10/2004 → 9/2006

+

Research & Teacher, Université de Provence, Marseille

+

teach 1/2, research 1/6, C++ development 1/3

+

DEES ; a C++ software

+
+

7500 lines of C++ code, 10.000 with comments

+

Main functionalities:

+
    +
  • Mulitiplicity Automata, HMM & PDA Inference,
  • +
  • Baum Welch & Viterbi Algorithms,
  • +
  • GraphViz export,
  • +
  • String Generation from many Models,
  • +
+ + + + + + + + + + + + + + + +
LanguagesC++
APISTL
EnvironmentLinux (Debian) & Windows XP
+
+
+
+

Moniteur des Universités 10/2001 → 9/2004

+

Université de Provence, Marseille

+

teach 1/3, research 1/3, C++ Development 1/3

+

Creation of DEES (see preceeding entry).

+ +
+
+
+

Diploma

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
2004Ph.D. degree in Machine Learning
2001D.E.A. in Computer Science (equivalent to master)
2000Maîtrise d’Informatique
1999Licence in Computer Science
1998DEUG MIAS (math)
1995BAC S (math)
+ +
+
+

Scientific Publications

+
+

International

+ + + + + + + + + + + + + + + +
Journals[Fundamenta Inforamticæ vol.86 2008] [Pattern Recognition, 2004]
Conferences[ECML 2008] [COLT 2006] [ICGI 2006] [COLT 2004] [ICALP 2003] [ICGI 2002]
Workshop[TAGI05]
+
+
+

National (French)

+ + + + + + + + + + + + + + + +
Journals[JEDAI, 2003]
Conferences[CAP 2006] [CAP 2004] [CAP 2003]
Thesis[Université de Provence 2004]
+ +
+
+
+

Projects

+

Most of my latest programming activities are publicly available at github.com/yogsototh

+
+

Haskell libraries

+
    +
  • Link to list of packages

  • +
  • holy-project
  • +
  • human-readable-duration
  • +
  • wai-middleware-caching-lru
  • +
  • wai-middleware-caching-redis
  • +
  • wai-middleware-caching

  • +
+
+
+

YPassword 2008 →

+

Mainly an iOS application:

+ +

I've done fully the website from scratch. Also there are some javascript implementation of YPassword method:

+
+
    +
  • a Mac OS X dashboard widget,
  • +
  • a Cappuccino Web application,
  • +
  • a jQuery Web application,
  • +
  • a command line tool,
  • +
  • an Applescript helper
  • +
+
+
+
+

Anonymous Network Project 02/2006 → 06/2006

+

Made a protocol similar to TOR for student.

+
+
+

Other projects

+
    +
  • Web Application used for private team usage at AirFrance 2008 → This application is just done for teh lulz. Not related to the Airfrance work. But still pleasant. Javascript(Prototype.js, Scriptaculous), CSS, PHP/MySQL, Google Talk
  • +
  • metapost package to draw Automata 2003 → 2004 metapost
  • +
  • Mac OS X Screensavers (YClock & YAquaBubbles) 2003 → 2004 Objective-C,Quartz Composer,Cocoa
  • +
+

You could find even more information by looking at:

+ +
+
+
+
+
    +
  1. To be more precise in Grammatical Inference

  2. +
  3. http://vigiglobe.com

  4. +
  5. https://github.com/yogsototh/DEES

  6. +
  7. http://yann.esposito.free.fr/dees.php?lang=en

  8. +
  9. http://labh-curien.univ-st-etienne.fr/SEDiL/faq.php?lang=en (I like to believe I became a better designer ☺)

  10. +
  11. Sorry the code is private I can't show it :(.

  12. +
  13. http://ypassword.espozito.com

  14. +
  15. http://yannesposito.com/Scratch/en/blog/

  16. +
+
+
+ + + + + + + + diff --git a/src/Scratch/files/styling.css b/src/Scratch/files/styling.css new file mode 100644 index 0000000..3202bb5 --- /dev/null +++ b/src/Scratch/files/styling.css @@ -0,0 +1,296 @@ +@charset "UTF-8"; +/* + * I add this to html files generated with pandoc. + */ +html { + font-size: 100%; + overflow-y: scroll; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; } + +body { + color: #444; + font-family: "Avenir Next", Helvetica, Arial, sans-serif; + font-size: 12px; + line-height: 1.7; + padding: 1em; + margin: auto; + max-width: 42em; + background: #fefefe; } + +a { + color: #e67e22; + text-decoration: none; } + +a:visited { + color: #d35400; } + +a:hover { + color: #e67e22; + text-decoration: underline; } + +a:active { + color: #e67e22; } + +a:focus { + outline: thin dotted; } + +*::-moz-selection { + background: rgba(255, 255, 0, 0.3); + color: #000; } + +*::selection { + background: rgba(255, 255, 0, 0.3); + color: #000; } + +a::-moz-selection { + background: rgba(255, 255, 0, 0.3); + color: #0645ad; } + +a::selection { + background: rgba(255, 255, 0, 0.3); + color: #0645ad; } + +p { + margin: 1em 0; } + +img { + max-width: 100%; } + +h1, h2, h3, h4, h5, h6 { + color: #111; + line-height: 125%; + margin-top: 2em; + font-family: "Hoefler Text", Georgia, Times, serif; } + +h4, h5, h6 { + font-weight: bold; } + +h1 { + font-size: 2.5em; } + +h2 { + font-size: 2em; } + +h3 { + font-size: 1.5em; } + +h4 { + font-size: 1.2em; } + +h5 { + font-size: 1em; } + +h6 { + font-size: 0.9em; } + +blockquote { + color: #666666; + margin: 0; + padding-left: 3em; + border-left: 0.5em #EEE solid; } + +hr { + display: block; + height: 2px; + border: 0; + border-top: 1px solid #aaa; + border-bottom: 1px solid #eee; + margin: 1em 0; + padding: 0; } + +pre, code, kbd, samp { + color: #000; + font-family: monospace, monospace; + _font-family: 'courier new', monospace; + font-size: 0.98em; + background: #f0f0f0; } + +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; + padding: 1em 2em; + border-left: solid 4px #C6E5Fd; + border-bottom: solid 1px #CCC; } + +b, strong { + font-weight: bold; } + +dfn { + font-style: italic; } + +ins { + background: #ff9; + color: #000; + text-decoration: none; } + +mark { + background: #ff0; + color: #000; + font-style: italic; + font-weight: bold; } + +sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; } + +sup { + top: -0.5em; } + +sub { + bottom: -0.25em; } + +ul, ol { + margin: 1em 0; + padding: 0 0 0 2em; } + +li p:last-child { + margin-bottom: 0; } + +ul ul, ol ol { + margin: .3em 0; } + +dl { + margin-bottom: 1em; } + +dt { + font-weight: bold; + margin-bottom: .8em; } + +dd { + margin: 0 0 .8em 2em; } + +dd:last-child { + margin-bottom: 0; } + +img { + border: solid 1px #888; + -ms-interpolation-mode: bicubic; + vertical-align: middle; } + +figure { + display: block; + text-align: center; + margin: 1em 0; } + +figure img { + border: none; + margin: 0 auto; } + +figcaption { + font-size: 0.8em; + font-style: italic; + margin: 0 0 .8em; } + +table { + margin-bottom: 2em; + border-bottom: 1px solid; + border-top: 1px solid; + border-spacing: 0; + border-collapse: collapse; + width: 100%; } + +table th { + padding: .2em 1em; + background-color: #eee; + border-bottom: 1px solid; } + +table td { + padding: .2em 1em; + vertical-align: top; } + +.author { + font-size: 1.2em; + text-align: center; } + +@media only screen and (min-width: 480px) { + body { + font-size: 14px; } } +@media only screen and (min-width: 768px) { + body { + font-size: 16px; } } +@media print { + * { + background: transparent !important; + color: black !important; + filter: none !important; + -ms-filter: none !important; } + + body { + font-size: 12pt; + max-width: 100%; } + + a, a:visited { + text-decoration: underline; } + + hr { + height: 1px; + border: 0; + border-bottom: 1px solid black; } + + a[href]:after { + content: " (" attr(href) ")"; } + + abbr[title]:after { + content: " (" attr(title) ")"; } + + .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { + content: ""; } + + pre, blockquote { + border: 1px solid #999; + padding-right: 1em; + page-break-inside: avoid; } + + tr, img { + page-break-inside: avoid; } + + img { + max-width: 100% !important; } + + @page :left { + margin: 15mm 20mm 15mm 10mm; } + @page :right { + margin: 15mm 10mm 15mm 20mm; } + p, h2, h3 { + orphans: 3; + widows: 3; } + + h2, h3 { + page-break-after: avoid; } } +#TOC ul { + overflow-x: hidden; + list-style: none; } + #TOC ul li a { + background: #fff; } + #TOC ul li:before { + float: left; + width: .7em; + white-space: nowrap; + content: "-   . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . "; } + +header { + text-align: center; + margin-bottom: 5em; + border-bottom: solid; + border-top: solid; + padding-top: 2em; + padding-bottom: 2em; } + header h1.title { + margin-top: 0; } + header .date { + font-size: 1em; } + +.footnotes { + font-size: .9em; } + +#footer { + font-size: 1.5em; + border-top: solid; + padding-top: 1em; + margin-top: 2em; } + +/*# sourceMappingURL=styling.css.map */ diff --git a/src/Scratch/fr/about/index.html b/src/Scratch/fr/about/index.html new file mode 100644 index 0000000..213cfa5 --- /dev/null +++ b/src/Scratch/fr/about/index.html @@ -0,0 +1,117 @@ + + + + + YBlog - Qui est derrière ce site? + + + + + + + + + + + + + + + +
+ + +
+

Qui est derrière ce site?

+
+
+
+
+

Avatar

+

@ yann.esposito@gmail.com
+ @yogsototh
+ yogsototh
+ yogsototh
+ yogsototh
+ yogsototh

+
+

ADA: DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp

+

Une photo

+
+C’est moi
C’est moi
+
+

En quelques mots

+

Je suis un passionné. Passionné par :

+ +

Mais avant tout j’adore apprendre. Par exemple, j’ai appris de nombreux langages de programmation: Haskell, Clojure, javascript, C, C++, Objective-C, Python, Ruby, Java, Perl, awk, bash, zsh, LaTeX, metapost, camL, Scheme

+

Mon CV

+

Anciennes

+ +
+ +
+
+ +
+ Yann Esposito© +
+
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/01_nanoc/index.html b/src/Scratch/fr/blog/01_nanoc/index.html new file mode 100644 index 0000000..216152c --- /dev/null +++ b/src/Scratch/fr/blog/01_nanoc/index.html @@ -0,0 +1,109 @@ + + + + + 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.

+
+
+ + + +
+
+ Published on 2008-10-10 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/02_ackgrep/index.html b/src/Scratch/fr/blog/02_ackgrep/index.html new file mode 100644 index 0000000..197f844 --- /dev/null +++ b/src/Scratch/fr/blog/02_ackgrep/index.html @@ -0,0 +1,126 @@ + + + + + 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

+ +

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 :

+ +

Pour mon utilisation personnelle et celle de mon équipe c’est suffisant. J’espère que ça pourra vous aider.

+
+
+ + + +
+
+ Published on 2009-07-22 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/03_losthighway/index.html b/src/Scratch/fr/blog/03_losthighway/index.html new file mode 100644 index 0000000..63e221e --- /dev/null +++ b/src/Scratch/fr/blog/03_losthighway/index.html @@ -0,0 +1,196 @@ + + + + + YBlog - Lost Highway démystifié (un peu) + + + + + + + + + + + + + + + + +
+ + +
+

Lost Highway démystifié (un peu)

+

introduction

+ +
+
+
+
+
+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 ?

+
+
+ + + +
+
+ Published on 2009-08-04 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/04_drm/index.html b/src/Scratch/fr/blog/04_drm/index.html new file mode 100644 index 0000000..57cad27 --- /dev/null +++ b/src/Scratch/fr/blog/04_drm/index.html @@ -0,0 +1,118 @@ + + + + + YBlog - Les protections anti-copies sont LE MAL + + + + + + + + + + + + + + + + +
+ + +
+

Les protections anti-copies sont LE MAL

+

Protections anti-copie = Belle connerie (+1) !

+ +
+
+
+
+

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’.

+
+
+ + + +
+
+ Published on 2009-08-15 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/05_git_create_remote_branch/index.html b/src/Scratch/fr/blog/05_git_create_remote_branch/index.html new file mode 100644 index 0000000..754130b --- /dev/null +++ b/src/Scratch/fr/blog/05_git_create_remote_branch/index.html @@ -0,0 +1,122 @@ + + + + + YBlog - Création de branches externe avec Git + + + + + + + + + + + + + + + + +
+ + +
+

Création de branches externe avec Git

+ +
+
+
+
+

Edit: Maintenant j’utilise git push -u

+

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 :

+ +

Bien sûr, je suppose qu’origin est déjà configurée.

+
+
+ + + +
+
+ Published on 2009-08-17 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/06_How_I_use_git/index.html b/src/Scratch/fr/blog/06_How_I_use_git/index.html new file mode 100644 index 0000000..e4df714 --- /dev/null +++ b/src/Scratch/fr/blog/06_How_I_use_git/index.html @@ -0,0 +1,219 @@ + + + + + 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.

+ +

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 : + +

Ensuite, il faut placer ce projet dans un répertoire accessible via Internet.

+ +
+

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 :

+ +

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 :

+ +

Ensuit vous pouvez travailler en faisant (plusieurs fois) :

+ +

Quang vous voulez envoyez les modifications locales sur Internet, il suffit de faire :

+ +

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 :

+ +

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) :

+ + +
+
+ + + +
+
+ Published on 2009-08-18 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/07_Screensaver_compilation_option_for_Snow_Leopard/index.html b/src/Scratch/fr/blog/07_Screensaver_compilation_option_for_Snow_Leopard/index.html new file mode 100644 index 0000000..7f7d137 --- /dev/null +++ b/src/Scratch/fr/blog/07_Screensaver_compilation_option_for_Snow_Leopard/index.html @@ -0,0 +1,112 @@ + + + + + YBlog - Compilation d'économiseur d'écran sous OS X<small>©</small> + + + + + + + + + + + + + + + + +
+ + +
+

Compilation d'économiseur d'écran sous OS X©

+ +
+
+
+
+

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.

+
+
+ + + +
+
+ Published on 2009-09-06 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/index.html b/src/Scratch/fr/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/index.html new file mode 100644 index 0000000..727aabf --- /dev/null +++ b/src/Scratch/fr/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/index.html @@ -0,0 +1,159 @@ + + + + + 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 :

+ +

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 :

+
+ +
+

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.

+
+
+ + + +
+
+ Published on 2009-09-07 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/09_Why_I_didn-t_keep_whosamung-us/index.html b/src/Scratch/fr/blog/09_Why_I_didn-t_keep_whosamung-us/index.html new file mode 100644 index 0000000..30aaca2 --- /dev/null +++ b/src/Scratch/fr/blog/09_Why_I_didn-t_keep_whosamung-us/index.html @@ -0,0 +1,115 @@ + + + + + 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 +
+
+
+
+ + + +
+
+ Published on 2009-09-11 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/10_Synchronize_Custom_WebSite_with_mobileMe/index.html b/src/Scratch/fr/blog/10_Synchronize_Custom_WebSite_with_mobileMe/index.html new file mode 100644 index 0000000..4e06597 --- /dev/null +++ b/src/Scratch/fr/blog/10_Synchronize_Custom_WebSite_with_mobileMe/index.html @@ -0,0 +1,331 @@ + + + + + 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"
+
+
+
+ + + +
+
+ Published on 2009-09-11 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/11_Load_Disqus_Asynchronously/index.html b/src/Scratch/fr/blog/11_Load_Disqus_Asynchronously/index.html new file mode 100644 index 0000000..36612be --- /dev/null +++ b/src/Scratch/fr/blog/11_Load_Disqus_Asynchronously/index.html @@ -0,0 +1,140 @@ + + + + + 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:

+ +

by

+ +

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.

+
+
+ + + +
+
+ Published on 2009-09-17 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/index.html b/src/Scratch/fr/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/index.html new file mode 100644 index 0000000..96aa32d --- /dev/null +++ b/src/Scratch/fr/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/index.html @@ -0,0 +1,127 @@ + + + + + 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 :

+ +

par (si vous utilisez jQuery) :

+ +

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à.

+
+
+ + + +
+
+ Published on 2009-09-28 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/2009-09-jQuery-Tag-Cloud/index.html b/src/Scratch/fr/blog/2009-09-jQuery-Tag-Cloud/index.html new file mode 100644 index 0000000..6a28aee --- /dev/null +++ b/src/Scratch/fr/blog/2009-09-jQuery-Tag-Cloud/index.html @@ -0,0 +1,288 @@ + + + + + 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:

+ +

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:

+ +

and a div containing links associtated to this tag:

+ +
+

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.

+ +

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

+ +

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

+ +

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.

+ +

Finaly a function to generate the XHTML/jQuery code

+
+ +
+

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

+ +

That’s all folks.

+
+
+ + + +
+
+ Published on 2009-09-23 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/2009-09-replace-all-except-some-part/index.html b/src/Scratch/fr/blog/2009-09-replace-all-except-some-part/index.html new file mode 100644 index 0000000..6ba9a7c --- /dev/null +++ b/src/Scratch/fr/blog/2009-09-replace-all-except-some-part/index.html @@ -0,0 +1,169 @@ + + + + + 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

+ +

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

+ +

An usage is:

+ +

A better syntax would be:

+ +

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.

+
+
+ + + +
+
+ Published on 2009-09-22 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/index.html b/src/Scratch/fr/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/index.html new file mode 100644 index 0000000..67236f3 --- /dev/null +++ b/src/Scratch/fr/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/index.html @@ -0,0 +1,182 @@ + + + + + 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 – "\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 ! $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 }
+
+

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=( $(diff $localRef $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.yrf $ydestRep/map.yrf # remove the temporary file }

+

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.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.html $webroot/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. +
+
+
+
+ + + +
+
+ Published on 2009-10-28 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/2009-10-30-How-to-handle-evil-IE/index.html b/src/Scratch/fr/blog/2009-10-30-How-to-handle-evil-IE/index.html new file mode 100644 index 0000000..b4bdd64 --- /dev/null +++ b/src/Scratch/fr/blog/2009-10-30-How-to-handle-evil-IE/index.html @@ -0,0 +1,142 @@ + + + + + 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(’ + +
+
+ + + +
+
+ Published on 2011-08-25 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/Parsec-Presentation/index.html b/src/Scratch/fr/blog/Parsec-Presentation/index.html new file mode 100644 index 0000000..7078fe5 --- /dev/null +++ b/src/Scratch/fr/blog/Parsec-Presentation/index.html @@ -0,0 +1,334 @@ + + + + + YBlog - Parsec Presentation + + + + + + + + + + + + + + + + +
+ + +
+

Parsec Presentation

+ +
+
+
+
+

AST
+

+
+

tlpl: Une introduction rapide à Parsec. Un parser en Haskell.

+
+
    +
  • The html presentation is here.
  • +
+
+() () () () () ( +) ( +

) () () () () () () () () ()

+
+ +
+
+

+Parsec +

+by Yann Esposito + + +
+
+
+

+Parsing +

+

+Latin pars (ōrātiōnis), meaning part (of speech). +

+
    +
  • +analysing a string of symbols +
  • +
  • +formal grammar. +
  • +
+
+
+

+Parsing in Programming Languages +

+

+Complexity: +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+Method + +Typical Example + +Output Data Structure +
+Splitting + +CSV + +Array, Map +
+Regexp + +email + +
    +
  • Fixed Layout Tree +
+Parser + +Programming language + +
    +
  • Most Data Structure +
+
+
+

+Parser & culture +

+

+In Haskell Parser are really easy to use. +

+

+Generally: +

+
    +
  • +In most languages: split then regexp then parse +
  • +
  • +In Haskell: split then parse +
  • +
+
+
+

+Parsing Example +

+

+From String: +

+
(1+3)*(1+5+9)
+

+To data structure: +

+

+AST
+

+
+
+

+Parsec +

+
+

+Parsec lets you construct parsers by combining high-order Combinators to create larger expressions. +

+

+Combinator parsers are written and used within the same programming language as the rest of the program. +

+

+The parsers are first-class citizens of the languages […]" +

+

+Haskell Wiki +

+
+
+
+

+Parser Libraries +

+

+In reality there are many choices: +

+ + + + + + + + + + + + + + + +
+attoparsec + +fast +
+Bytestring-lexing + +fast +
+Parsec 3 + +powerful, nice error reporting +
+
+
+

+Haskell Remarks (1) +

+

+spaces are meaningful +

+
f x   -- ⇔ f(x) in C-like languages
+f x y -- ⇔ f(x,y)
+
+
+

+Haskell Remarks (2) +

+

+Don’t mind strange operators (<*>, <$>).
Consider them like separators, typically commas.
They are just here to deal with types. +

+

+Informally: +

+
toto <$> x <*> y <*> z
+-- ⇔ toto x y z
+-- ⇔ toto(x,y,z) in C-like languages
+
+
+

+Minimal Parsec Examples +

+
whitespaces = many (oneOf "\t ")
+number = many1 digit
+symbol = oneOf "!#$%&|*+-/:<=>?@^_~"
+
+" \t " – whitespaces on " \t " "" – whitespaces on “32” “32” – number on “32”
+
+
+– number on " 
+          
+
+ + + +
+
+ Published on 2013-10-09 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/Password-Management/index.html b/src/Scratch/fr/blog/Password-Management/index.html new file mode 100644 index 0000000..dd74e51 --- /dev/null +++ b/src/Scratch/fr/blog/Password-Management/index.html @@ -0,0 +1,169 @@ + + + + + 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 : +
  • +
+

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.

+
+
+ + + +
+
+ Published on 2011-05-18 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/Rational-Web-Framework-Choice/index.html b/src/Scratch/fr/blog/Rational-Web-Framework-Choice/index.html new file mode 100644 index 0000000..53a68ad --- /dev/null +++ b/src/Scratch/fr/blog/Rational-Web-Framework-Choice/index.html @@ -0,0 +1,2296 @@ + + + + + YBlog - Rational Web Framework Choice + + + + + + + + + + + + + + + + +
+ + +
+

Rational Web Framework Choice

+ +
+
+
+
+
+Main image +
+
+

tlpl: Comment déterminer de la façon la plus rationnelle possible le meilleur framework work relativement à vos besoins. Cliquez ici pour aller au résultats. Cet article n’est disponible qu’en anglais.

+
+

This is it.
+You’ve got the next big idea.
+You just need to make a very simple web application.

+

It sounds easy! You just need to choose a good modern web framework, when suddenly:

+
+[Choice Paralysis][choice_paralysis] +
+Choice Paralysis +
+
+

After your brain stack overflowed, you decide to use a very simple methodology. Answer two questions:

+

Which language am I familiar with?
+What is the most popular web framework for this language?

+

Great! This is it.

+

But, you continually hear this little voice.

+
+

“You didn’t made a bad choice, yes. But …
+you hadn’t made the best either.”

+
+

This article try to determine in the most objective and rational way the best(s) web framework(s) depending on your needs. To reach this goal, I will provide a decision tool in the result section.

+

I will use the following methodology:

+

Methodology

+
    +
  1. Model how to make choice +
      +
    1. choose important parameters
    2. +
    3. organize (hierarchize) them
    4. +
    5. write down an objective chooser
    6. +
  2. +
  3. Grab objective quantified informations about web frameworks relatively to choosen parameters
  4. +
  5. Sanitize your data in order to handle imprecisions, lack of informations…
  6. +
  7. Apply the model of choice to your informations
  8. +
+
+

☞ Important Note
+I am far from happy to the actual result. There are a lot of biases, for example in the choice of the parameters. The same can be said about the data I gathered. I am using very imprecise informations. But, as far as I know, this is the only article which use many different parameters to help you choose a web framework.

+

This is why I made a very flexible decision tool:

+

Decision tool.

+
+

Model

+

Here are the important features (properties/parameters) I selected to make the choice:

+
    +
  1. Popularity, which correlate with: +
      +
    • number of tested libraries
    • +
    • facility to find learning material
    • +
    • ability to find another developer to work with
    • +
  2. +
  3. Efficiency, which is generally correlated to: +
      +
    • how much processing power you’ll need per user
    • +
    • maintenance price per user
    • +
    • how long the user will wait to see/update data
    • +
  4. +
  5. Expressiveness, which is generally correlated to: +
      +
    • faster development
    • +
    • flexibility, adaptability
    • +
  6. +
  7. Robustness, which correlate with: +
      +
    • security
    • +
    • fewer bugs
    • +
  8. +
+

Each feature is quite important and mostly independant from each other. I tried to embrace most important topics concerning web frameworks with these four properties. I am fully concious some people might lack another important feature. Nonetheless the methodology used here can be easily replicated. If you lack an important property add it at will and use this choice method.

+

Also each feature is very hard to measure with precision. This is why we will only focus on order of magnitude.

+

For each property a framework could have one of the six possible values: Excellent, Very Good, Good, Medium, Bad or Very Bad

+

So how to make a decision model from these informations?

+

One of the most versatile method is to give a weight for each cluster value. And to select the framework maximizing this score:

+
score(framework) = efficiency + robustness + expressiveness + popularity
+
+

For example:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Expressiveness1071-∞-∞-∞
Popularity554321
Efficiency1086421
Robustness1086421
+

Using this weighted table, that means:

+
    +
  • we discard the three least expressive clusters.
  • +
  • We don’t make any difference between excellent and very good in popularity.
  • +
  • Concerning efficient framework in excellent cluster will have 2 more points than the “very good” cluster.
  • +
+

So for each framework we compute its score relatively to a weighted table. And we select the best(s).

+

Example: Using this hypothetic framework and the preceeding table.

+ + + + + + + + + + + + + + + + + + + +
ExpressivenessPopularityEfficiencyRobustness
yogExcellentVery BadMediumVery Good
+
score(yog) = 10 + 0 + 4 + 8 = 22
+
+

Most needs should be expressed by such a weighted table. In the result section, we will discuss this further.

+

It is now time to try to get these measures.

+

Objective measures

+

None of the four properties I choosen can be measured with perfect precision. But we could get the order of magnitude for each.

+

I tried to focus on the framework only. But it is often easier to start by studying the language first.

+

For example, I have datas about popularity by language and I also have different datas concerning popularity by framework. Even if I use only the framework focused datas in my final decision model, it seemed important to me to discuss about the datas for the languages. The goal is to provide a tool to help decision not to give a decision for you.

+

Popularity

+

RedMonk Programming Language Rankings (January 2013) provide an apparent good measure of popularity. While not perfect the current measure feel mostly right. They create an image using stack overflow and github data. Vertical correspond to the number of questions on stackoverflow. Horizontal correspond to the number of projects on github.

+

If you look at the image, your eye can see about four clusters. The 1 cluster correspond to mainstream languages:

+
+Mainstream Languages Cluster from [RedMonk][redmonk] +
+Mainstream Languages Cluster from RedMonk +
+
+

Most developer know at least one of these language.

+

The second cluster is quite bigger. It seems to correspond to languages with a solid community behind them.

+
+Second tier languages from [RedMonk][redmonk] +
+Second tier languages from RedMonk +
+
+

I don’t get into detail, but you could also see third and fourth tier popular languages.

+

So:

+

Mainstream: JavaScript, Java, PHP, Python, Ruby, C#, C++, C, Objective-C, Perl, Shell

+

Good: Scala, Haskell, Visual Basic, Assembly, R, Matlab, ASP, ActionScript, Coffeescript, Groovy, Clojure, Lua, Prolog

+

Medium: Erlang, Go, Delphi, D, Racket, Scheme, ColdFusion, F#, FORTRAN, Arduino, Tcl, Ocaml

+

Bad: third tier Very Bad: fourth tier

+

I don’t thing I could find easily web frameworks for third or fourth tier languages.

+

For now, I only talked about language popularity. But what about framework popularity? I made a test using number of question on stackoverflow only. Then by dividing by two for each 6 cluster:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework#nb%
ExcellentRubyRails176208100%
Very GoodPythonDjango57385<50%
JavaServlet54139
JavaSpring31641
Node.jsnode.js27243
PHPCodeigniter21503
GroovyGrails20222
GoodRubySinatra8631<25%
PythonFlask7062
PHPLaravel6982
PHPKohana5959
Node.jsExpress5009
MediumPHPCake4554<13%
C♯ServiceStack3838
ScalaPlay3823
JavaWicket3819
DartDart3753
PHPSlim3361
PythonTornado3321
ScalaLift2844
GoGo2689
BadJavaTapestry1197<6%
C♯aspnet1000
HaskellYesod889
PHPSilex750
PHPLithium732
C♯nancy705
Very badJavaGrizzly622<3%
ErlangCowboy568
PerlDancer496
PHPSymphony2491
GoRevel459
ClojureCompojure391
PerlMojolicious376
ScalaScalatra349
ScalaFinagle336
PHPPhalcon299
jsRingo299
JavaGemini276
HaskellSnap263
PerlPlack257
ErlangElli230
JavaDropwizard188
PHPYaf146
JavaPlay1133
Node.jsHapi131
JavaVertx60
ScalaUnfiltered42
Conion18
Clojurehttp-kit17
PerlKelp16
PHPMicromvc13
LuaOpenresty8
C++cpoll-cppsp5
ClojureLuminus3
PHPPhreeze1
+

As we can see, our framework popularity indicator can be quite different from its language popularity. For now I didn’t found a nice way to merge the results from RedMonk with these one. So I’ll use these unperfect one. Hopefully the order of magninute is mostly correct for most framework.

+

Efficiency

+

Another objective measure is efficiency. We all know benchmarks are all flawed. But they are the only indicators concerning efficiency we have.

+

I used the benchmark from benchmarksgame. Mainly, there are five clusters:

+ + + + + + + + + + + + + + + + + + + + + + + +
1x→2xC, C++
2x→3xJava 7, Scala, OCamL, Haskell, Go, Common LISP
3x→10xC♯, Clojure, Racket, Dart
10x→30xErlang
30x→PHP, Python, Perl, Ruby, JRuby
+

Remarks concerning some very slow languages:

+
    +
  • PHP ; huge variations, can be about 1.5x C speed in best case.
  • +
  • Python ; huge variations, can be about 1.5x C speed in best case
  • +
  • Perl ; Can be about 3x C speed in best case
  • +
  • Ruby, JRuby ; mostly very slow.
  • +
+

This is a first approach. The speed of the language for basic benchmarks. But, here we are interrested in web programming. Fortunately techempower has made some tests focused on most web frameworks:

+

Web framework benchmarks.

+

These benchmark doesn’t fit well with our needs. The values are certainly quite imprecise to your real usage. The goal is just to get an order of magnitude for each framework. Another problem is the high number of informations.

+

As always, we should remember these informations are also imprecise. So I simply made some classes of efficiency.

+

Remark: I separated the clusters by using power of 2 relatively to the fastest.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework#nbslowness
ExcellentC++cpoll-cppsp114,711
Javgemini105,204
Luaopenresty93,882
Javservlet90,580
C++cpoll-pool89,167
Gogo76,024
Scafinagle68,413
Gorevel66,990
Javrest-express63,209
Very GoodJavwicket48,772>2×
Scascalatra48,594
Cljhttp-kit42,703
Javspring36,643>3×
PHPphp36,605
Javtapestry35,032
Cljcompojure32,088
JSringo31,962
Javdropwizard31,514
Cljluminus30,672
GoodScaplay-slick29,950>4×
Scaunfiltered29,782
Erlelli28,862
Javvertx28,075
JSnodejs27,598
Erlcowboy24,669
Conion23,649
Hklyesod23,304
JSexpress22,856>5×
Scaplay-scala22,372
Jav grizzly-jersey20,550
Pytornado20,372>6×
PHPphalcon18,481
Grvgrails18,467
Prlplack16,647>7×
PHPyaf14,388
MediumJShapi11,235>10×
Javplay19,979
Hklsnap9,196
Prlkelp8,250
Pyflask8,167
Javplay-java7,905
Jav play-java-jpa7,846
PHPmicromvc7,387
Prldancer5,040>20×
Prlmojolicious4,371
JSringo-conv4,249
Pydjango4,026
PHPcodeigniter3,809>30×
BadRbyrails3,445
Scalift3,311
PHPslim3,112
PHPkohana2,378>40×
PHPsilex2,364
Very BadPHPlaravel1,639>60×
PHPphreeze1,410
PHPlithium1,410
PHPfuel1,410
PHPcake1,287>80×
PHPsymfony2879>100×
C#aspnet-mvc871
Rbysinatra561>200×
C#servicestack51
Dardart0
C#nancy0
Prlweb-simple0
+

These are manually made clusters. But you get the idea. Certainly, some framework could jump between two different clusters. So this is something to remember. But as always, the order of magnitude is certainly mostly right.

+

Expressiveness

+

Now, how to objectively measure expressiveness?

+

RedMonk had a very good idea to find an objective (while imprecise) measure of each language expressiveness. Read this article for details.

+

After filtering languages suitable for web development, we end up with some clusters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguages
ExcellentCoffeescript, Clojure, Haskell
Very GoodRacket, Groovy, R, Scala, OCamL, F♯, Erlang, Lisp, Go
MediumPerl, Python, Objective-C, Scheme, Tcl, Ruby
BadLua, Fortran (free-format), PHP, Java, C++, C♯
Very BadAssembly, C, Javascript,
+

Unfortunately there is no information about dart. So I simply give a very fast look at the syntax. As it looked a lot like javascript and js is quite low. I decided to put it close to java.

+

Also an important remark, javascript score very badly here while coffeescript (compiling to js) score “excellent”. So if you intend to use a javascript framework but only with coffescript that should change substantially the score. As I don’t believe it is the standard. Javascript oriented framework score very badly regarding expressiveness.

+
+Click here to show/hide the table for frameworks +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework
ExcellentCljluminus
Cljhttp-kit
Cljcompojure
Hklsnap
Hklyesod
Very GoodErlelli
Erlcowboy
Gogo
Gorevel
Grvgrails
Scalift
Scafinagle
Scascalatra
Scaplay-scala
Scaplay-slick
Scaunfiltered
MediumPrlkelp
Prlplack
Prldancer
Prlweb-simple
Prlmojolicious
Pyflask
Pydjango
Pytornado
Rbyrails
Rbysinatra
BadC#nancy
C#aspnet-mvc
C#servicestack
C++cpoll-pool
C++cpoll-cppsp
Dardart
Javplay1
Javvertx
Javgemini
Javspring
Javwicket
Javservlet
Javtapestry
Javplay-java
Javdropwizard
Javrest-express
Javplay-java-jpa
Javgrizzly-jersey
Luaopenresty
PHPphp
PHPyaf
PHPcake
PHPfuel
PHPslim
PHPsilex
PHPkohana
PHPlaravel
PHPlithium
PHPphalcon
PHPphreeze
PHPmicromvc
PHPsymfony2
PHPcodeigniter
Very BadConion
JShapi
JSringo
JSnodejs
JSexpress
JSringo-conv
+
+

Robustness

+

I couldn’t find any complete study to give the number of bug relatively to each framework/language.

+

But one thing I saw from experience is the more powerful the type system the safest your application is. While the type system doesn’t remove completely the need to test your application a very good type system tend to remove complete classes of bug.

+

Typically, not using pointer help to reduce the number of bugs due to bad references. Also, using a garbage collector, reduce greatly the probability to access unallocated space.

+
+Static Type Properties from [James IRY Blog][typesanalysis] +
+Static Type Properties from James IRY Blog +
+
+

From my point of view, robustness is mostly identical to safety.

+

Here are the clusters:

+ + + + + + + + + + + + + + + + + + + +
ExcellentHaskell, Scheme, Erlang
Very GoodScala, Java, Clojure
GoodRuby, Python, Groovy, javascript, PHP
MediumC++, C#, Perl, Objective-C, Go, C
+

So applying this to frameworks gives the following clusters:

+
+Click here to show/hide the table for frameworks +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework
ExcellentErlelli
Erlcowboy
Hklsnap
Hklyesod
Very GoodCljluminus
Cljhttp-kit
Cljcompojure
Javplay1
Javvertx
Javgemini
Javspring
Javwicket
Javservlet
Javtapestry
Javplay-java
Javdropwizard
Javrest-express
Javplay-java-jpa
Javgrizzly-jersey
Scalift
Scafinagle
Scascalatra
Scaplay-scala
Scaplay-slick
Scaunfiltered
GoodGrvgrails
JShapi
JSringo
JSnodejs
JSexpress
JSringo-conv
Luaopenresty
PHPphp
PHPyaf
PHPcake
PHPfuel
PHPslim
PHPsilex
PHPkohana
PHPlaravel
PHPlithium
PHPphalcon
PHPphreeze
PHPmicromvc
PHPsymfony2
PHPcodeigniter
Pyflask
Pydjango
Pytornado
Rbyrails
Rbysinatra
MediumConion
C#nancy
C#aspnet-mvc
C#servicestack
C++cpoll-pool
C++cpoll-cppsp
Dardart
Gogo
Gorevel
Prlkelp
Prlplack
Prldancer
Prlweb-simple
Prlmojolicious
+
+

The result

+

For the result I initialized the table with my own needs.

+

And I am quite happy it confirms my current choice. I sware I didn’t given yesod any bonus point. I tried to be the most objective and factual as possible.

+

Now, it is up to you to enter your preferences.

+

On each line you could change how important a feature is for you. From essential to unsignificant. Of course you could change the matrix at will.

+

I just show a top 10 frameworks. In order to give a more understandable measure I provide the log of the score.

+ + + + + + + + + + + + + + + + + + + + + + + +
+ +Excellent + +Very good + +Good + +Medium + +Bad + +Very bad + +Importance +
+Expressiveness +
+Popularity +
+Efficiency +
+Robustness +
+
+Click to force refresh +
+
+ +
+ + +

I didn’t had the courage in explaining in what the scoring system is good. Mostly, if you use product instead of sum for the score you could use power of e for the values in the matrix. And you could see the matrix as a probability matrix (each line sum to 1). Which provide a slighly better intuition on whats going on.

+

Remember only that values are exponential. Do not double an already big value for example the effect would be extreme.

+

Conclusion

+

All of this is based as most as I could on objective data. The choice method seems both rather rational and classical. It is now up to you to edit the score matrix to set your needs.

+

I know that in the current state there are many flaws. But it is a first system to help make a choice rationally.

+

I encourage you to go further if you are not satisfied by my method.

+

The source code for the matrix shouldn’t be too hard to read. Just read the source of this webpage. You could change the positionning of some frameworks if you believe I made some mistake by placing them in some bad clusters.

+

So I hope this tool will help you in making your life easier.

+
+
+ + + +
+
+ Published on 2013-08-06 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/SVG-and-m4-fractals/index.html b/src/Scratch/fr/blog/SVG-and-m4-fractals/index.html new file mode 100644 index 0000000..551a6f6 --- /dev/null +++ b/src/Scratch/fr/blog/SVG-and-m4-fractals/index.html @@ -0,0 +1,229 @@ + + + + + YBlog - Accroître le pouvoir des languages déficients. + + + + + + + + + + + + + + + + +
+ + +
+

Accroître le pouvoir des languages déficients.

+

Fractales en SVG avec m4

+ +
+
+
+
+
+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 :
  • +
+ +
    +
  • Afficher quelquechose. Au lieu de print "Hello world!", xslt nous offre :
  • +
+ +
    +
  • afficher la valeur d’une variable, au lieu de print myvar, nous avons droit à :
  • +
+ +
    +
  • Essayez d’imaginer à quel point il est verbeux de déclarer une fonction dans ce langage.
  • +
+

La solution (m4 à la rescousse)

+ +

Maintenant compilons simplement ce fichier :

+ +

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 :

+ +

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.

+
+
+ + + +
+
+ Published on 2011-10-20 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/Safer-Haskell-Install/index.html b/src/Scratch/fr/blog/Safer-Haskell-Install/index.html new file mode 100644 index 0000000..3598b1b --- /dev/null +++ b/src/Scratch/fr/blog/Safer-Haskell-Install/index.html @@ -0,0 +1,149 @@ + + + + + YBlog - Installer Haskell + + + + + + + + + + + + + + + + +
+ + +
+

Installer Haskell

+ +
+
+
+
+
+to Haskell and Beyond!!! +
+
+

tlpl: Pour installer Haskell (OS X et Linux) copiez/collez les lignes suivante dans un terminal :

+
curl https://raw.githubusercontent.com/yogsototh/install-haskell/master/install-haskell.sh | sudo zsh
+

Si vous êtes sous windows, téléchargez Haskell Platform et suivez les instructions pour utiliser Haskell LTS.

+

Si vous voulez savoir le pourquoi et le comment ; lisez le reste de l’article.

+
+

Pourquoi ?

+

La plus grande faiblesse d’Haskell n’a rien à voir avec le langage en lui-même mais avec son écosystème.

+

The main problem I’ll try to address is the one known as cabal hell. The community is really active in fixing the issue. I am very confident that in less than a year this problem will be one of the past. But to work today, I provide an install method that should reduce greatly two effects of cabal hell:

+
    +
  • dependency error
  • +
  • lost time in compilation (poor polar bears)
  • +
+

With my actual installation method, you should minimize your headache and almost never hit a dependency error. But there could exists some. If you encounter any dependency error, ask gently to the package manager to port its package to stackage.

+

So to install copy/paste the following three lines in your terminal:

+
curl https://raw.githubusercontent.com/yogsototh/install-haskell/master/install-haskell.sh | sudo zsh
+

How?

+

You can read the script and you will see that this is quite straightforward.

+
    +
  1. It downloads the latest GHC binary for you system and install it.
  2. +
  3. It does the same with the cabal program.
  4. +
  5. It updates your cabal config file to use Haskell LTS.
  6. +
  7. It enable profiling to libraries and executables.
  8. +
  9. It installs some useful binaries that might cause compilation error if not present.
  10. +
+

As the version of libraries is fixed up until you update the Haskell LTS version, you should never use cabal sandbox. That way, you will only compile each needed library once. The compiled objects/binaries will be in your ~/.cabal directory.

+

Some Last Words

+

This script use the latest Haskell LTS. So if you use this script at different dates, the Haskell LTS might have changed.

+

While it comes to cabal hell, some solutions are sandboxes and nix. Unfortunately, sandboxes didn’t worked good enough for me after some time. Furthermore, sandboxes forces you to re-compile everything by project. If you have three yesod projects for example it means a lot of time and CPU. Also, nix didn’t worked as expected on OS X. So fixing the list of package to a stable list of them seems to me the best pragmatic way to handle the problem today.

+

From my point of view, Haskell LTS is the best step in the right direction. The actual cabal hell problem is more a human problem than a tool problem. This is a bias in most programmer to prefer resolve social issues using tools. There is nothing wrong with hackage and cabal. But for a package manager to work in a static typing language as Haskell, packages must work all together. This is a great strength of static typed languages that they ensure that a big part of the API between packages are compatible. But this make the job of package managing far more difficult than in dynamic languages.

+

People tend not to respect the rules in package numbering1. They break their API all the time. So we need a way to organize all of that. And this is precisely what Haskell LTS provide. A set of stable packages working all together. So if a developer break its API, it won’t work anymore in stackage. And whether the developer fix its package or all other packages upgrade their usage. During this time, Haskell LTS end-users will be able to develop without dependency issues.

+
+

+The image of the cat about to jump that I slightly edited can found here +

+
+
+
    +
  1. I myself am guilty of such behavior. It was a beginner error.

  2. +
+
+
+
+ + + +
+
+ Published on 2014-08-16 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/Social-link-the-right-way/index.html b/src/Scratch/fr/blog/Social-link-the-right-way/index.html new file mode 100644 index 0000000..4cc684a --- /dev/null +++ b/src/Scratch/fr/blog/Social-link-the-right-way/index.html @@ -0,0 +1,300 @@ + + + + + YBlog - Être correct avec les boutons share + + + + + + + + + + + + + + + + +
+ + +
+

Être correct avec les boutons share

+ +
+
+
+
+
+Main image +
+ +

The problem

+

Ever been on a website and want to tweet about it? Fortunately, the website might have a button to help you. But do you really know what this button do?

+

The “Like”, “Tweet” and “+1” buttons will call a javascript. It will get access to your cookies. It helps the provider of the button to know who you are.

+

In plain English, the “+1” button will inform Google you are visiting the website, even if you don’t click on “+1”. The same is true for the “like” button for facebook and the “tweet this” button for twitter.

+

The problem is not only a privacy issue. In fact (sadly imho) this isn’t an issue for most people. These button consume computer ressources. Far more than a simple link. It thus slow down a bit the computer and consume energy. These button could also slow down the rendering of your web page.

+

Another aspect is their design. Their look and feel is mostly imposed by the provider.

+

The most problematic aspect in my opinion is to use a third party js on your website. What if tomorrow twitter update their tweet button? If the upgrade break something for only a minority of people, they won’t fix it. This could occur anytime without any notification. They just have to add a document.write in their js you call asynchronously and BAM! Your website is just an empty blank page. And as you call many external ressources, it can be very difficult to find the origin of the problem.

+

Using social network buttons:

+
    +
  • Pros: +
      +
    • help user share your website,
    • +
    • can provide a popularity indicator to your users.
    • +
  • +
  • Cons: +
      +
    • you help tracking your users,
    • +
    • generally doesn’t follow the design of your website,
    • +
    • use more computer ressources,
    • +
    • slow down your website,
    • +
    • executing third party js can break things silently.
    • +
  • +
+

Solutions

+

I will provide you two solutions with the following properties:

+
    +
  • Pros: +
      +
    • help user share your website,
    • +
    • doesn’t follow your user,
    • +
    • use almost no computer ressource,
    • +
    • doesn’t slow down your website,
    • +
    • doesn’t execute any third party js on your website.
    • +
  • +
  • Cons: +
      +
    • doesn’t provide any popularity information.
    • +
  • +
+

Solution 1 (no js):

+ +

But you have to replace $url$ by the current url.

+

Solution 2 (Just copy/paste):

+

If you don’t want to write the url yourself, you could use some minimal js:

+ +

Here is the result:

+
+ + +
+

Good looking solutions

+

If you don’t want just text but nice icons. You have many choices:

+
    +
  • Use images <img src="..."/> in the links.
  • +
  • Use icon fonts
  • +
+

As the first solution is pretty straightforward, I’ll explain the second one.

+
    +
  1. Download the icon font here
  2. +
  3. put the font file(s) at some place (here ‘fonts/social_font.ttf’ relatively to your css file)
  4. +
  5. Add this to your css
  6. +
+ +

Now add this to your html:

+

Solution 1 (without js):

+ +

Solution 2 (same with a bit more js):

+ +

Here is the result:

+
+
+

· ·

+
+ +
+

Conclusion

+
    +
  1. You get your design back,
  2. +
  3. You stop to help tracking people,
  4. +
  5. You use less computer ressources and more generally power ressources which is good for the planet,
  6. +
  7. Your web pages will load faster.
  8. +
+

ps: On my personal website I continue to use Google analytics. Therefore, Google (and only Google, not facebook nor twitter) can track you here. But I might change this in the future.

+
+
+ + + +
+
+ Published on 2013-03-14 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/Typography-and-the-Web/index.html b/src/Scratch/fr/blog/Typography-and-the-Web/index.html new file mode 100644 index 0000000..9f1001c --- /dev/null +++ b/src/Scratch/fr/blog/Typography-and-the-Web/index.html @@ -0,0 +1,169 @@ + + + + + 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 :

+ +

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. +
+
+
+
+ + + +
+
+ Published on 2012-02-02 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/Vim-as-IDE/index.html b/src/Scratch/fr/blog/Vim-as-IDE/index.html new file mode 100644 index 0000000..a1fff21 --- /dev/null +++ b/src/Scratch/fr/blog/Vim-as-IDE/index.html @@ -0,0 +1,333 @@ + + + + + YBlog - Vim as IDE + + + + + + + + + + + + + + + + +
+ + +
+

Vim as IDE

+ +
+
+
+
+
+Main image +
+
+

tlpl: Comment utiliser vim comme une IDE très efficace

+

In Learn Vim Progressively I’ve show how Vim is great for editing text, and navigating in the same file (buffer). In this short article you’ll see how I use Vim as an IDE. Mainly by using some great plugins.

+
+

Vim Plugin Manager

+

There are a lot of Vim plugins. To manage them I use vim-plug.

+

To install it:

+ +
+

☞ Note I have two parts in my .vimrc. The first part contains the list of all my plugins. The second part contains the personal preferences I setted for each plugin. I’ll separate each part by ... in the code.

+
+

Survival

+

Colorscheme

+
+Solarized theme +
+

Before anything, you should protect your eyes using a readable and low contrast colorscheme.

+

For this I use solarized dark. To add it, you only have to write this in your ~/.vimrc file:

+
call plug#begin('~/.vim/plugged')
+
+Plug 'altercation/vim-colors-solarized'
+
+call plug#end()
+
+" -- solarized personal conf
+set background=dark
+try
+    colorscheme solarized
+catch
+endtry
+

Minimal hygiene

+

You should be able to see and destroy trailing whitespaces.

+
+Trim whitespaces +
+
Plug 'bronson/vim-trailing-whitespace'
+

You can clean trailing whitespace with :FixWhitespace.

+

And also you should see your 80th column.

+
if (exists('+colorcolumn'))
+    set colorcolumn=80
+    highlight ColorColumn ctermbg=9
+endif
+
+80th column +
+

File Management

+

One of the most important hidden skills in programming is the ability to search and find files in your projects.

+

The majority of people use something like NERDTree. This is the classical left column with a tree of files of your project. I stopped to use this. And you should probably too.

+

I switched to unite. No left column lost. Faster to find files. Mainly it works like Spotlight on OS X.

+

First install ag (the silver search). If you don’t know ack or ag your life is going to be upgraded. This is a simple but essential tool. It is mostly a grep on steroids.

+
" Unite
+"   depend on vimproc
+"   ------------- VERY IMPORTANT ------------
+"   you have to go to .vim/plugin/vimproc.vim and do a ./make
+"   -----------------------------------------
+Plug 'Shougo/vimproc.vim'
+Plug 'Shougo/unite.vim'
+
+...
+
+let g:unite_source_history_yank_enable = 1
+try
+  let g:unite_source_rec_async_command='ag --nocolor --nogroup -g ""'
+  call unite#filters#matcher_default#use(['matcher_fuzzy'])
+catch
+endtry
+" search a file in the filetree
+nnoremap <space><space> :split<cr> :<C-u>Unite -start-insert file_rec/async<cr>
+" reset not it is <C-l> normally
+:nnoremap <space>r <Plug>(unite_restart)
+

Now type space twice. A list of files appears. Start to type some letters of the file you are searching for. Select it, type return and bingo the file opens in a new horizontal split.

+
+Unite example +
+

If something goes wrong just type <space>r to reset the unite cache.

+

Now you are able to search file by name easily and efficiently.

+

Now search text in many files. For this you use ag:

+
Plug 'rking/ag.vim'
+...
+" --- type ° to search the word in all files in the current dir
+nmap ° :Ag <c-r>=expand("<cword>")<cr><cr>
+nnoremap <space>/ :Ag
+

Don’t forget to add a space after the :Ag.

+

These are two of the most powerful shortcut for working in a project. using ° which is nicely positioned on my azerty keyboard. You should use a key close to *.

+

So what ° is doing? It reads the string under the cursor and search for it in all files. Really useful to search where a function is used.

+

If you type <space>/ followed by a string, it will search for all occurrences of this string in the project files.

+

So with this you should already be able to navigate between files very easily.

+

Language Agnostic Plugins

+

Git

+
+Show modified lines +
+

Show which line changed since your last commit.

+
Plug 'airblade/vim-gitgutter'
+

And the “defacto” git plugin:

+
Plug 'tpope/vim-fugitive'
+

You can reset your changes from the latest git commit with :Gread. You can stage your changes with :Gwrite.

+
+Reset changes +
+

Align things

+
Plug 'junegunn/vim-easy-align'
+
+...
+
+" Easy align interactive
+vnoremap <silent> <Enter> :EasyAlign<cr>
+

Just select and type Return then space. Type Return many type to change the alignments.

+

If you want to align the second column, Return then 2 then space.

+
+Easy align example +
+

Basic auto completion: C-n & C-p

+

Vim has a basic auto completion system. The shortcuts are C-n and C-p while you are in insert mode. This is generally good enough in most cases. For example when I open a file not in my configured languages.

+

Haskell

+

My current Haskell programming environment is great!

+

Each time I save a file, I get a comment pointing to my errors or proposing me how to improve my code.

+

So here we go:

+
+

☞ Don’t forget to install ghc-mod with: cabal install ghc-mod

+
+
" ---------- VERY IMPORTANT -----------
+" Don't forget to install ghc-mod with:
+" cabal install ghc-mod
+" -------------------------------------
+
+Plug 'scrooloose/syntastic'             " syntax checker
+" --- Haskell
+Plug 'yogsototh/haskell-vim'            " syntax indentation / highlight
+Plug 'enomsg/vim-haskellConcealPlus'    " unicode for haskell operators
+Plug 'eagletmt/ghcmod-vim'
+Plug 'eagletmt/neco-ghc'
+Plug 'Twinside/vim-hoogle'
+Plug 'pbrisbin/html-template-syntax'    " Yesod templates
+
+...
+
+" -------------------
+"       Haskell
+" -------------------
+let mapleader="-"
+let g:mapleader="-"
+set tm=2000
+nmap <silent> <leader>ht :GhcModType<CR>
+nmap <silent> <leader>hh :GhcModTypeClear<CR>
+nmap <silent> <leader>hT :GhcModTypeInsert<CR>
+nmap <silent> <leader>hc :SyntasticCheck ghc_mod<CR>:lopen<CR>
+let g:syntastic_mode_map={'mode': 'active', 'passive_filetypes': ['haskell']}
+let g:syntastic_always_populate_loc_list = 1
+nmap <silent> <leader>hl :SyntasticCheck hlint<CR>:lopen<CR>
+
+" Auto-checking on writing
+autocmd BufWritePost *.hs,*.lhs GhcModCheckAndLintAsync
+
+"  neocomplcache (advanced completion)
+autocmd BufEnter *.hs,*.lhs let g:neocomplcache_enable_at_startup = 1
+function! SetToCabalBuild()
+    if glob("*.cabal") != ''
+        set makeprg=cabal\ build
+    endif
+endfunction
+autocmd BufEnter *.hs,*.lhs :call SetToCabalBuild()
+
+" -- neco-ghc
+let $PATH=$PATH.':'.expand("~/.cabal/bin")
+

Just enjoy!

+
+hlint on save +
+

I use - for my leader because I use , a lot for its native usage.

+
    +
  • -ht will highlight and show the type of the block under the cursor.
  • +
  • -hT will insert the type of the current block.
  • +
  • -hh will unhighlight the selection.
  • +
+
+Auto typing on save +
+

Clojure

+
+Rainbow parenthesis +
+

My main language at work is Clojure. And my current vim environment is quite good. I lack the automatic integration to lein-kibit thought. If I have the courage I might do it myself one day. But due to the very long startup time of clojure, I doubt I’ll be able to make a useful vim plugin.

+

So mainly you’ll have real rainbow-parenthesis (the default values are broken for solarized).

+

I used the vim paredit plugin before. But it is too restrictive. Now I use sexp which feel more coherent with the spirit of vim.

+
" " -- Clojure
+Plug 'kien/rainbow_parentheses.vim'
+Plug 'guns/vim-clojure-static'
+Plug 'guns/vim-sexp'
+Plug 'tpope/vim-repeat'
+Plug 'tpope/vim-fireplace'
+
+...
+
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesActivate
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadRound
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadSquare
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadBraces
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl setlocal iskeyword+=?,-,*,!,+,/,=,<,>,.,:
+" -- Rainbow parenthesis options
+let g:rbpt_colorpairs = [
+	\ ['darkyellow',  'RoyalBlue3'],
+	\ ['darkgreen',   'SeaGreen3'],
+	\ ['darkcyan',    'DarkOrchid3'],
+	\ ['Darkblue',    'firebrick3'],
+	\ ['DarkMagenta', 'RoyalBlue3'],
+	\ ['darkred',     'SeaGreen3'],
+	\ ['darkyellow',  'DarkOrchid3'],
+	\ ['darkgreen',   'firebrick3'],
+	\ ['darkcyan',    'RoyalBlue3'],
+	\ ['Darkblue',    'SeaGreen3'],
+	\ ['DarkMagenta', 'DarkOrchid3'],
+	\ ['Darkblue',    'firebrick3'],
+	\ ['darkcyan',    'SeaGreen3'],
+	\ ['darkgreen',   'RoyalBlue3'],
+	\ ['darkyellow',  'DarkOrchid3'],
+	\ ['darkred',     'firebrick3'],
+	\ ]
+

Working with Clojure will becomre quite smoother. You can eval any part of your code, you must launch a Clojure REPL manually in another terminal thought.

+

Last words

+

I hope it will be useful.

+

Last but not least, if you want to use my vim configuration you can get it here:

+

github.com/yogsototh/vimrc

+
+
+ + + +
+
+ Published on 2014-12-07 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/Yesod-excellent-ideas/index.html b/src/Scratch/fr/blog/Yesod-excellent-ideas/index.html new file mode 100644 index 0000000..61cba8a --- /dev/null +++ b/src/Scratch/fr/blog/Yesod-excellent-ideas/index.html @@ -0,0 +1,177 @@ + + + + + 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 :

+ +

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.

+ +

Comme AnotherPageR est une URL elle ne pourra contiendra pas (par défaut) de caractère dangereux comme par exemple :

+ +

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 :

+ +

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:

+ +

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.

+ +

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 :

+ +

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 !

+
+
+ + + +
+
+ Published on 2011-10-04 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/Yesod-tutorial-for-newbies/index.html b/src/Scratch/fr/blog/Yesod-tutorial-for-newbies/index.html new file mode 100644 index 0000000..aaab3eb --- /dev/null +++ b/src/Scratch/fr/blog/Yesod-tutorial-for-newbies/index.html @@ -0,0 +1,483 @@ + + + + + YBlog - Haskell web programming + + + + + + + + + + + + + + + + +
+ + +
+

Haskell web programming

+

A Yesod tutorial

+ +
+
+
+
+
+Neo Flying at warp speed +
+
+

update: updated for yesod 1.2

+

tl;dr: A simple yesod tutorial. Yesod is a Haskell web framework. You shouldn’t need to know Haskell.

+
+

Why Haskell?

+
+Impressive Haskell Benchmark +
+

Its efficiency (see Snap Benchmark & Warp Benchmark1). Haskell is an order of magnitude faster than interpreted languages like Ruby and Python2.

+

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.js3.

+
+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 not yet an heroku for Haskell. In fact, I use heroku to host my websites but this isn’t straightforward (see the how to).
  • +
+

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 requests 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.

+

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:

+ +

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:

+ +

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:

+ +

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.

+ +
+

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.

+
+

Fortunately, there is already a .gitignore file into the yosog folder. You just have to initialize your git repository:

+ +

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.

+
+
~/Sites/yosog $ yesod add-handler
+Name of route (without trailing R): Echo
+Enter route pattern (ex: /entry/#EntryId): /echo/#String
+Enter space-separated list of methods (ex: GET POST): GET
+

Almost all work is done for us. The add-handler do the following:

+

Update the config/route file by appending:

+
/echo/#String EchoR GET
+

This line contains three elements: the url pattern, a handler name, an http method.

+
    +
  • create a Handler/Echo.hs file
  • +
  • import Handler.Echo in the main Application.hs file
  • +
  • declare Handler.Echo in the cabal file for building the application
  • +
+

Now try to go to localhost:3000/echo/foo. You should get a message explaining getEchoR is not yet implemented.

+

So let’s take a look at Handler/Echo.hs:

+ +

This should be straightforward. Now we changes it by:

+ +

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:

+ +

You can click on it to test it.

+

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.

+ +

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 Data.Text instead of String
  • +
  • Put our “views”4 inside the template directory
  • +
+

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):

+ +

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:

+ +

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:

+ +

and modify the handler Handler/Echo.hs:

+ +

At this point, our web application is nicely structured. 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 handler:

+
 ~/Sites/yosog (master) $ yesod add-handler
+Name of route (without trailing R): Mirror
+Enter route pattern (ex: /entry/#EntryId): /mirror
+Enter space-separated list of methods (ex: GET POST): GET POST
+

This time the path /mirror will accept GET and POST requests. Update the corresponding new Handler file (Handler/Mirror.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 (templates/mirror.hamlet and templates/posted.hamlet):

+ + +

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 <script>alert('Bad');</script>). 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.

+

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.
  • +
+

As before add some handlers

+
~/Sites/yosog (master) $ yesod add-handler
+Name of route (without trailing R): Blog
+Enter route pattern (ex: /entry/#EntryId): /blog
+Enter space-separated list of methods (ex: GET POST): GET POST
+
+~/Sites/yosog (master) $ yesod add-handler
+Name of route (without trailing R): Article
+Enter route pattern (ex: /entry/#EntryId): /blog/#ArticleId
+Enter space-separated list of methods (ex: GET POST): GET
+

Then 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. 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.

+ +

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.

+ +

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.

+ +

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:

+ +

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.

+

You could take a first look by clicking here. Of course, you could not post something yet.

+

Get back to Handler/Blog.hs.

+ +

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:

+ +

Finally we need to display an article. For this we will modify Handler/Article.hs

+ +

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:

+ +

The blog system is finished. Just for fun, you can try to create an article with the following content:

+ +

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. Read my tutorial Learn Haskell Fast and Hard on School of Haskell or directly on this blog
  4. +
  5. Then read the excellent Learn you a Haskell for Great Good
  6. +
  7. If you have difficulties in understanding concepts like monads, you should really read these articles. For me they were enlightening.
  8. +
  9. 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.
  10. +
+

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. 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.

  2. +
  3. 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.

  4. +
  5. 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.

  6. +
  7. By view I mean yesod widget’s hamlet, lucius and julius files.

  8. +
+
+
+
+ + + +
+
+ Published on 2012-01-15 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/feed/feed.xml b/src/Scratch/fr/blog/feed/feed.xml new file mode 100644 index 0000000..87e525c --- /dev/null +++ b/src/Scratch/fr/blog/feed/feed.xml @@ -0,0 +1,5661 @@ + + + yannesposito.com + + + http://yannesposito.com/Scratch/fr/blog/feed/feed.xml + + Yann Esposito + yann.esposito@gmail.com + + 2016-10-01T00:00:00Z + + Tips in avoiding Haskell Success at all cost + + http://yannesposito.com/Scratch/fr/blog/Helping-avoid-Haskell-Success/index.html + 2016-10-01T00:00:00Z + 2016-10-01T00:00:00Z + +Main image +
+
+

Few days ago there were about 20 job offer for Haskell. In only one day! How is that possible? As a real haskeller, I find this situation unbearable!

+

After all, we must avoid success at all cost. And I’ll help SPJ achieve this honorable goal.

+
+

Prevent Interest from beginner

+

Imagine a situation were you see people demonstrating some interest in learning Haskell.

+

Quick! Prevent them from going further.

+

If they come from the dynamic (uni-typed) languages like Python, Javascript…:

+
+

Haskell? A statically typed language??? Hmm… You mean like C and Java?

+
+

Such a remark should immediately shut down any interest in Haskell.

+

If they want to produce application with them:

+
+

Haskell? Isn’t it only a language for student! I don’t think it is useful for REAL WORLD applications!

+
+

If they just want to learn something new:

+
+

Haskell? Ah yes, I remember, mostly they only have the equivalent of Java interfaces and they stopped there. They don’t even have classes!!!! Can you imagine? I don’t even speak about class inheritance.

+

We’re in 2016! And they don’t even support basic Object Oriented Programming. What a joke!

+
+

If they love low level programming:

+
+

Haskell? Ah yes, I heard that lazyness make it impossible to think about code complexity and generally cause lot of space leaks.

+
+

And if it is not enough:

+
+

Haskell? Ah yes. I’m not fan of their Stop the World GC.

+
+

If they come from LISP and the statically typed language remark wasn’t enough. Try to mention the lack of macros in Haskell. Don’t mention template Haskell or even less Generics and all recent progress in GHC.

+

Make it difficult to install

+

Many hints there:

+
    +
  • Send them on another compiler than GHC
  • +
  • Explain that they should never use a binary distribution of GHC! And they must compile it manually! It might not stop them but it will make the installation process much more tedious.
  • +
  • Lie! Explain there is a severe security issue with latest tools. Explain they must use cabal-install 1.18 or older.
  • +
  • Also explain them that in order to be able to handle lib dependencies correctly they MUST first learn Nix! Never talk about stack, cabal freeze, … While Nix is great, forcing new user completely alien to all these concepts to first learn it before starting to write their first line of code can greatly reduce their enthusiasm. Bonus point if you make them believe you can only program in Haskell on NixOS.
  • +
+

Make it difficult to learn

+

Make new comers feel dumb

+

The very first thing to do is to explain how Haskell is so easy to learn. How natural it is for everybody you know. And except someone you always considered very dumb, everybody was very productive in Haskell in few hours.

+

Use vocabulary alien to them as much as possible. Here is a list of terms you should use in the very first minutes of your description of Haskell:

+
    +
  • catamorphism (bonus if you mention that the word come from the Greek κατα for catastrophe, that way you’ll look like a snob and you also use the word catastrophe in a Haskell context).
  • +
  • Monad! Of course you should use it ASAP! And explain they are exactly like potatoes or bananas shaped chairs. Double bonus if you explain that monad are really simple as they are just a monoid in the category of endofunctors.
  • +
  • GADTs
  • +
  • Yoneda Lemma
  • +
  • Homotopy Type Theory
  • +
  • +
+

Each of this term will hopefully be intimidating.

+

Tutorial authors

+

Please don’t provide an obvious first example like:

+ +

Instead prefer a fully servant example:

+ +

This nice example should overflow the number of new concepts a Haskell newcomer should deal with:

+
    +
  • Language extensions. Each extension can take a lot of time to be explained.
  • +
  • Strange notations: +
      +
    • :<|>
    • +
    • '[] instead of []
    • +
  • +
  • Proxy
  • +
  • Immediate usage of $
  • +
  • deriving ha ha! You’ll need to explain typeclasses first!
  • +
  • the definition for getItemById
  • +
+

Of course use the most of your energy explaining the language extensions first. Use a great deal of details and if possible use as much as possible references to Category Theory. You’ll get bonus points if you mention HoTT! Double bonus points if you explain that understanding all details in HoTT is essential to use Haskell on a daily basis.

+

Explain that what this does is incredible but for the wrong reasons. For example don’t mention why instance ToJSON Item is great. But insist that we achieved to serve a JSON with extreme elegance and simplicity. Keep insisting on the simplicity and forgot to mention type safety which is one of the main benefit of Servant.

+

If you’re afraid that this example might be too close to a real world product, you can simply use some advanced lenses examples:

+ +

Certainly a great example to start a new language with.

+

Library authors

+
    +
  1. Do your best not to respect versioning number policy to maximize the probability to break things.
  2. +
  3. Don’t write any documentation, type are enough!
  4. +
  5. Even better, add mistakes to your documentation
  6. +
  7. Each time you can use a meaningful notation, make it wrong. For example, if you have a symmetric relation use an asymmetric symbol to represent it.
  8. +
  9. If possible remove all function names and only use symbols of at least 5 letters: For example you can replace your function log :: Level -> String -> IO () by (<=.=$$.).
  10. +
+

If the the trends continue toward growth, then we might need to go further at the risk of breaking our own ecosystem:

+
    +
  • Split your libs as much as possible. The best would be to use one lib by symbol
  • +
  • Use unsafePerformIO as much as possible
  • +
  • Push to Hackage a version not accessible on your public repository
  • +
  • modify the package on Hackage using the same version but with incompatible API
  • +
  • Add memory leaks
  • +
  • Add bugs
  • +
  • Add back doors and publish how to use them
  • +
+

Yes we said, at all cost!

+

Conclusion & Mistake

+

So with all of this I believe we should be on the right track to avoid success at all cost!

+

Sorry? What?

+

Oh… Apparently I made a precedence mistake!

+

SPJ didn’t asked to avoid success $ at all cost but to avoid $ success at all cost1.

+

Sorry! My bad! Forget about all of this. Keep the good work everybody! Haskell is certainly one of the most awesome language in the world! Its community is also just great.

+

I’m really happy to see it growth every year. Thanks to all contributors making it possible to still have a lot of fun after many years using Haskell!

+

And the fact that in Haskell the right choice is preferred to the easiest choice, certainly helped.

+
+
+
    +
  1. A good point to use more LISP syntax.

  2. +
+
]]> + + + Haskell Tutorials, a tutorial + + http://yannesposito.com/Scratch/fr/blog/Haskell-Tutorials--a-tutorial/index.html + 2016-05-06T00:00:00Z + 2016-05-06T00:00:00Z + +Main image +
+
+

tl;dr: Some hints on how to make great documentation for Haskell libraries.

+
    +
  1. Create a Tutorial module containing nothing except documentation.
  2. +
  3. Mention the Tutorial module in your cabal description
  4. +
  5. Use doctest to check your documentation is up to date
  6. +
  7. For more complex real world examples, link to the source of some test.
  8. +
+
+

Great documentation make a big difference. A bad documentation could simply make people not using your lib.

+

My friend was learning Haskell. To start he tried a Haskell library to make a small application. The documentation was deprecated to the point he wasn’t able to make a basic example work. How do you believe he felt? What does he thought about Haskell in general?

+

So here are my hint on how to make a great documentation in Haskell.

+

Documentation can take many different form.

+
    +
  1. Tutorials/Guides – write some prose which friendly take a user by hand and help him
  2. +
  3. Examples – how to use each function
  4. +
  5. Generated API Documentation – haddock
  6. +
+

Hints

+

Tutorials/Guides

+
    +
  1. Create a new module named Tutorial (or Guide.GuideTopic)
  2. +
  3. Create a link to the tutorial in the cabal description
  4. +
  5. Create a link to the tutorial in your README
  6. +
  7. Here is an example some Tutorial module content:
  8. +
+ +

To prevent obsolescence of your tutorial, use doctest.

+

That way when you’ll do a stack test or cabal test you’ll get errors if some example doesn’t work anymore.

+

Examples (doctest)

+

doctest is a great way to provide examples in your code documentation. These example will then be used as tests. Apparently it comes from Python community.

+

To use doctest, this is very simple:

+ +

And to make it works simply verify you have a test bloc in your .cabal file looking like this:

+
test-suite doctest
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test
+  main-is: DocTest.hs
+  build-depend: base >= 4.7 && < 5
+              , <YOUR_LIBRARY> 
+              , Glob >= 0.7
+              , doctest >= 0.9.12
+

and in test/DocTest.hs simply use

+ +

Now stack test or cabal test will check the validity of your documentation.

+

Bonuses

+

Verifying documentation coverage

+
    +
  1. Install haddock stack install haddock or cabal install haddock
  2. +
  3. Launch haddock without output format:
  4. +
+
> haddock src/**/*.hs
+Haddock coverage:
+ 100% ( 15 / 15) in 'Data.Duration'
+ 100% (  3 /  3) in 'Data.Duration.Tutorial'
+

Continuous Integration

+

There are plenty of alternative solution. I provide the one I believe would be used by most people. So if you use github simply create an account on travis.

+

Add a .travis.yml file in your repo containing the content of the file here and remove the builds you don’t need. It will build your project using a lot of different GHC versions and environemnts.

+

If you are afraid by such its complexity you might just want to use this one:

+ +

Don’t forget to activate your repo in travis.

+

For some bonus points add the build status badge in your README.md file:

+ +

Congratulation! Now if you break your documentation examples, you’ll get notified.

+

Badges

+

You could add badges to your README.md file.

+

Here is a list of some: shields.io

+

Hackage

+ +

Stackage

+

If you didn’t declared your package to stackage, please do it. It isn’t much work. Just edit a file to add your package. And you’ll could be able to add another badge:

+ +

See Stackage Badges for more informations.

+

Creating a new project with stack

+

If you use stack I suggest you to use the tasty-travis template. It will include the boilerplate for:

+
    +
  • tests
  • +
  • doctest
  • +
  • benchmark
  • +
  • travis CI
  • +
  • a README file to help you start
  • +
+

So edit your ~/.stack/config.yaml like this:

+
templates:
+  params:
+      author-name: Your Name
+      author-email: your@mail.com
+      copyright: 'Copyright: (c) 2016 Your Name'
+      github-username: yourusername
+      category: Development
+

And then you can create a new projec with:

+
stack new my-project tasty-travis
+

Generated Documentation

+

Even not doing anything, if you submit your library to hackage, haddock should generate some API documentation for free.

+

But to make real documentation you need to add some manual annotations.

+

Functions:

+
-- | My function description
+myFunction :: T1 -- ^ arg1 description
+           -> T2 -- ^ arg2 description
+myFunction arg1 arg2 = ...
+

Data:

+
data MyData a b
+  = C1 a b -- ^ doc for constructor C1
+  | C2 a b -- ^ doc for constructor C2
+
+data MyData a b
+  = C { a :: TypeA -- ^ field a description
+      , b :: TypeB -- ^ field b description
+      }
+

Module:

+
{-|
+Module    : MyModule
+Description: Short description
+Copyright : (c)
+License : MIT
+
+Here is a longer description of this module.
+With some code symbol @MyType@.
+And also a block of code:
+
+@
+data MyData = C Int Int
+
+myFunction :: MyData -> Int
+@
+
+-}
+

Documentation Structure:

+
module MyModule (
+  -- * Classes
+  C(..),
+  -- * Types
+  -- ** A data type
+  T,
+  -- ** A record
+  R,
+  -- * Some functions
+  f, g
+  ) where
+

That will generate headings.

+

Other Random Ideas

+

In Haskell we have great tools like hayoo! and hoogle.

+

And hackage and stackage provide also a lot of informations.

+

But generally we lack a lot of Tutorials and Guides. This post was an attempt to help people making more of them.

+

But there are other good ideas to help improve the situation.

+ +

In clojure when you create a new project using lein new my-project a directory doc is created for you. It contains a file with a link to this blog post:

+ +

Having a page by function/symbol with comments

+

If you try to search for some clojure function on a search engine there is a big chance the first result will link to:

+
    +
  • clojuredocs.org: try to search for reduce, update-in or index for example
  • +
+

For each symbol necessiting a documentation. You don’t only have the details and standard documentation. You’ll also get:

+
    +
  • Responsive Design (sometime you want to look at documentation on a mobile)
  • +
  • Contributed Examples
  • +
  • Contributed See Also section
  • +
  • Contributed notes/comments
  • +
+

clojuredocs.org is an independant website from the official Clojure website.

+

Most of the time, if you google the function you search you end up on clojuredocs for wich there are many contributions.

+

Currently stackage is closer to these feature than hackage. Because on stackage you have access to the README and also some comments by package.

+

I believe it would be more efficient to have at least a page by module and why not a page by symbol (data, functions, typeclasses…).

+

For example, we could provide details about foldl for example. Also as there would be less information to display, it will make the design cleaner.

+

Today, if you want to help documenting, you need to make a PR to the source of some library. While if we had an equivalent to clojuredocs for Haskell, adding documentation would simply be a few clicks away:

+
    +
  1. login
  2. +
  3. add/edit some example, comments, see-also section
  4. +
+

There are more than 23k people on /r/haskell. If only 1% of them would take 10 minutes adding a bit of documentation it will certainly change a lot of things in the percieved documentation quality.

+

And last but not least,

+

Design is important

+
+Design is Important +
+

Design is a vague word. A good design should care not only about how something look, but also how users will interact with it. For example by removing things to focus on the essential.

+

When I stumble upon some random blog post or random specification in the Haskell community, I had too much a feeling of old fashioned design.

+

If you look at node.js community lot of their web page look cleaner, easier to read and in the end, more user friendly.

+

Haskell is very different from node, I wouldn’t like to replace all long and precise documentation with short human unprecise concepts. I don’t want to transform scientific papers by tweets.

+

But like the scientific community has upgraded with the use of LaTeX, I believe we could find something similar that would make, very clean environment for most of us. A kind of look and feel that will be

+
    +
  • modern
  • +
  • device friendly (either on computer, mobile, tablet)
  • +
  • efficient, focus on what is most important and is helpful
  • +
]]> + + + Vim as IDE + + http://yannesposito.com/Scratch/fr/blog/Vim-as-IDE/index.html + 2014-12-07T00:00:00Z + 2014-12-07T00:00:00Z + +Main image + +
+

tlpl: Comment utiliser vim comme une IDE très efficace

+

In Learn Vim Progressively I’ve show how Vim is great for editing text, and navigating in the same file (buffer). In this short article you’ll see how I use Vim as an IDE. Mainly by using some great plugins.

+
+

Vim Plugin Manager

+

There are a lot of Vim plugins. To manage them I use vim-plug.

+

To install it:

+ +
+

☞ Note I have two parts in my .vimrc. The first part contains the list of all my plugins. The second part contains the personal preferences I setted for each plugin. I’ll separate each part by ... in the code.

+
+

Survival

+

Colorscheme

+
+Solarized theme +
+

Before anything, you should protect your eyes using a readable and low contrast colorscheme.

+

For this I use solarized dark. To add it, you only have to write this in your ~/.vimrc file:

+
call plug#begin('~/.vim/plugged')
+
+Plug 'altercation/vim-colors-solarized'
+
+call plug#end()
+
+" -- solarized personal conf
+set background=dark
+try
+    colorscheme solarized
+catch
+endtry
+

Minimal hygiene

+

You should be able to see and destroy trailing whitespaces.

+
+Trim whitespaces +
+
Plug 'bronson/vim-trailing-whitespace'
+

You can clean trailing whitespace with :FixWhitespace.

+

And also you should see your 80th column.

+
if (exists('+colorcolumn'))
+    set colorcolumn=80
+    highlight ColorColumn ctermbg=9
+endif
+
+80th column +
+

File Management

+

One of the most important hidden skills in programming is the ability to search and find files in your projects.

+

The majority of people use something like NERDTree. This is the classical left column with a tree of files of your project. I stopped to use this. And you should probably too.

+

I switched to unite. No left column lost. Faster to find files. Mainly it works like Spotlight on OS X.

+

First install ag (the silver search). If you don’t know ack or ag your life is going to be upgraded. This is a simple but essential tool. It is mostly a grep on steroids.

+
" Unite
+"   depend on vimproc
+"   ------------- VERY IMPORTANT ------------
+"   you have to go to .vim/plugin/vimproc.vim and do a ./make
+"   -----------------------------------------
+Plug 'Shougo/vimproc.vim'
+Plug 'Shougo/unite.vim'
+
+...
+
+let g:unite_source_history_yank_enable = 1
+try
+  let g:unite_source_rec_async_command='ag --nocolor --nogroup -g ""'
+  call unite#filters#matcher_default#use(['matcher_fuzzy'])
+catch
+endtry
+" search a file in the filetree
+nnoremap <space><space> :split<cr> :<C-u>Unite -start-insert file_rec/async<cr>
+" reset not it is <C-l> normally
+:nnoremap <space>r <Plug>(unite_restart)
+

Now type space twice. A list of files appears. Start to type some letters of the file you are searching for. Select it, type return and bingo the file opens in a new horizontal split.

+
+Unite example +
+

If something goes wrong just type <space>r to reset the unite cache.

+

Now you are able to search file by name easily and efficiently.

+

Now search text in many files. For this you use ag:

+
Plug 'rking/ag.vim'
+...
+" --- type ° to search the word in all files in the current dir
+nmap ° :Ag <c-r>=expand("<cword>")<cr><cr>
+nnoremap <space>/ :Ag
+

Don’t forget to add a space after the :Ag.

+

These are two of the most powerful shortcut for working in a project. using ° which is nicely positioned on my azerty keyboard. You should use a key close to *.

+

So what ° is doing? It reads the string under the cursor and search for it in all files. Really useful to search where a function is used.

+

If you type <space>/ followed by a string, it will search for all occurrences of this string in the project files.

+

So with this you should already be able to navigate between files very easily.

+

Language Agnostic Plugins

+

Git

+
+Show modified lines +
+

Show which line changed since your last commit.

+
Plug 'airblade/vim-gitgutter'
+

And the “defacto” git plugin:

+
Plug 'tpope/vim-fugitive'
+

You can reset your changes from the latest git commit with :Gread. You can stage your changes with :Gwrite.

+
+Reset changes +
+

Align things

+
Plug 'junegunn/vim-easy-align'
+
+...
+
+" Easy align interactive
+vnoremap <silent> <Enter> :EasyAlign<cr>
+

Just select and type Return then space. Type Return many type to change the alignments.

+

If you want to align the second column, Return then 2 then space.

+
+Easy align example +
+

Basic auto completion: C-n & C-p

+

Vim has a basic auto completion system. The shortcuts are C-n and C-p while you are in insert mode. This is generally good enough in most cases. For example when I open a file not in my configured languages.

+

Haskell

+

My current Haskell programming environment is great!

+

Each time I save a file, I get a comment pointing to my errors or proposing me how to improve my code.

+

So here we go:

+
+

☞ Don’t forget to install ghc-mod with: cabal install ghc-mod

+
+
" ---------- VERY IMPORTANT -----------
+" Don't forget to install ghc-mod with:
+" cabal install ghc-mod
+" -------------------------------------
+
+Plug 'scrooloose/syntastic'             " syntax checker
+" --- Haskell
+Plug 'yogsototh/haskell-vim'            " syntax indentation / highlight
+Plug 'enomsg/vim-haskellConcealPlus'    " unicode for haskell operators
+Plug 'eagletmt/ghcmod-vim'
+Plug 'eagletmt/neco-ghc'
+Plug 'Twinside/vim-hoogle'
+Plug 'pbrisbin/html-template-syntax'    " Yesod templates
+
+...
+
+" -------------------
+"       Haskell
+" -------------------
+let mapleader="-"
+let g:mapleader="-"
+set tm=2000
+nmap <silent> <leader>ht :GhcModType<CR>
+nmap <silent> <leader>hh :GhcModTypeClear<CR>
+nmap <silent> <leader>hT :GhcModTypeInsert<CR>
+nmap <silent> <leader>hc :SyntasticCheck ghc_mod<CR>:lopen<CR>
+let g:syntastic_mode_map={'mode': 'active', 'passive_filetypes': ['haskell']}
+let g:syntastic_always_populate_loc_list = 1
+nmap <silent> <leader>hl :SyntasticCheck hlint<CR>:lopen<CR>
+
+" Auto-checking on writing
+autocmd BufWritePost *.hs,*.lhs GhcModCheckAndLintAsync
+
+"  neocomplcache (advanced completion)
+autocmd BufEnter *.hs,*.lhs let g:neocomplcache_enable_at_startup = 1
+function! SetToCabalBuild()
+    if glob("*.cabal") != ''
+        set makeprg=cabal\ build
+    endif
+endfunction
+autocmd BufEnter *.hs,*.lhs :call SetToCabalBuild()
+
+" -- neco-ghc
+let $PATH=$PATH.':'.expand("~/.cabal/bin")
+

Just enjoy!

+
+hlint on save +
+

I use - for my leader because I use , a lot for its native usage.

+
    +
  • -ht will highlight and show the type of the block under the cursor.
  • +
  • -hT will insert the type of the current block.
  • +
  • -hh will unhighlight the selection.
  • +
+
+Auto typing on save +
+

Clojure

+
+Rainbow parenthesis +
+

My main language at work is Clojure. And my current vim environment is quite good. I lack the automatic integration to lein-kibit thought. If I have the courage I might do it myself one day. But due to the very long startup time of clojure, I doubt I’ll be able to make a useful vim plugin.

+

So mainly you’ll have real rainbow-parenthesis (the default values are broken for solarized).

+

I used the vim paredit plugin before. But it is too restrictive. Now I use sexp which feel more coherent with the spirit of vim.

+
" " -- Clojure
+Plug 'kien/rainbow_parentheses.vim'
+Plug 'guns/vim-clojure-static'
+Plug 'guns/vim-sexp'
+Plug 'tpope/vim-repeat'
+Plug 'tpope/vim-fireplace'
+
+...
+
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesActivate
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadRound
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadSquare
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadBraces
+autocmd BufEnter *.cljs,*.clj,*.cljs.hl setlocal iskeyword+=?,-,*,!,+,/,=,<,>,.,:
+" -- Rainbow parenthesis options
+let g:rbpt_colorpairs = [
+	\ ['darkyellow',  'RoyalBlue3'],
+	\ ['darkgreen',   'SeaGreen3'],
+	\ ['darkcyan',    'DarkOrchid3'],
+	\ ['Darkblue',    'firebrick3'],
+	\ ['DarkMagenta', 'RoyalBlue3'],
+	\ ['darkred',     'SeaGreen3'],
+	\ ['darkyellow',  'DarkOrchid3'],
+	\ ['darkgreen',   'firebrick3'],
+	\ ['darkcyan',    'RoyalBlue3'],
+	\ ['Darkblue',    'SeaGreen3'],
+	\ ['DarkMagenta', 'DarkOrchid3'],
+	\ ['Darkblue',    'firebrick3'],
+	\ ['darkcyan',    'SeaGreen3'],
+	\ ['darkgreen',   'RoyalBlue3'],
+	\ ['darkyellow',  'DarkOrchid3'],
+	\ ['darkred',     'firebrick3'],
+	\ ]
+

Working with Clojure will becomre quite smoother. You can eval any part of your code, you must launch a Clojure REPL manually in another terminal thought.

+

Last words

+

I hope it will be useful.

+

Last but not least, if you want to use my vim configuration you can get it here:

+

github.com/yogsototh/vimrc

]]>
+
+ + Installer Haskell + + http://yannesposito.com/Scratch/fr/blog/Safer-Haskell-Install/index.html + 2014-08-16T00:00:00Z + 2014-08-16T00:00:00Z + +to Haskell and Beyond!!! + +
+

tlpl: Pour installer Haskell (OS X et Linux) copiez/collez les lignes suivante dans un terminal :

+
curl https://raw.githubusercontent.com/yogsototh/install-haskell/master/install-haskell.sh | sudo zsh
+

Si vous êtes sous windows, téléchargez Haskell Platform et suivez les instructions pour utiliser Haskell LTS.

+

Si vous voulez savoir le pourquoi et le comment ; lisez le reste de l’article.

+
+

Pourquoi ?

+

La plus grande faiblesse d’Haskell n’a rien à voir avec le langage en lui-même mais avec son écosystème.

+

The main problem I’ll try to address is the one known as cabal hell. The community is really active in fixing the issue. I am very confident that in less than a year this problem will be one of the past. But to work today, I provide an install method that should reduce greatly two effects of cabal hell:

+
    +
  • dependency error
  • +
  • lost time in compilation (poor polar bears)
  • +
+

With my actual installation method, you should minimize your headache and almost never hit a dependency error. But there could exists some. If you encounter any dependency error, ask gently to the package manager to port its package to stackage.

+

So to install copy/paste the following three lines in your terminal:

+
curl https://raw.githubusercontent.com/yogsototh/install-haskell/master/install-haskell.sh | sudo zsh
+

How?

+

You can read the script and you will see that this is quite straightforward.

+
    +
  1. It downloads the latest GHC binary for you system and install it.
  2. +
  3. It does the same with the cabal program.
  4. +
  5. It updates your cabal config file to use Haskell LTS.
  6. +
  7. It enable profiling to libraries and executables.
  8. +
  9. It installs some useful binaries that might cause compilation error if not present.
  10. +
+

As the version of libraries is fixed up until you update the Haskell LTS version, you should never use cabal sandbox. That way, you will only compile each needed library once. The compiled objects/binaries will be in your ~/.cabal directory.

+

Some Last Words

+

This script use the latest Haskell LTS. So if you use this script at different dates, the Haskell LTS might have changed.

+

While it comes to cabal hell, some solutions are sandboxes and nix. Unfortunately, sandboxes didn’t worked good enough for me after some time. Furthermore, sandboxes forces you to re-compile everything by project. If you have three yesod projects for example it means a lot of time and CPU. Also, nix didn’t worked as expected on OS X. So fixing the list of package to a stable list of them seems to me the best pragmatic way to handle the problem today.

+

From my point of view, Haskell LTS is the best step in the right direction. The actual cabal hell problem is more a human problem than a tool problem. This is a bias in most programmer to prefer resolve social issues using tools. There is nothing wrong with hackage and cabal. But for a package manager to work in a static typing language as Haskell, packages must work all together. This is a great strength of static typed languages that they ensure that a big part of the API between packages are compatible. But this make the job of package managing far more difficult than in dynamic languages.

+

People tend not to respect the rules in package numbering1. They break their API all the time. So we need a way to organize all of that. And this is precisely what Haskell LTS provide. A set of stable packages working all together. So if a developer break its API, it won’t work anymore in stackage. And whether the developer fix its package or all other packages upgrade their usage. During this time, Haskell LTS end-users will be able to develop without dependency issues.

+
+

+The image of the cat about to jump that I slightly edited can found here +

+
+
+
    +
  1. I myself am guilty of such behavior. It was a beginner error.

  2. +
+
]]>
+
+ + Sacré Haskell Projet + + http://yannesposito.com/Scratch/fr/blog/Holy-Haskell-Starter/index.html + 2013-11-14T00:00:00Z + 2013-11-14T00:00:00Z + +Monty Python Holy Grail + +
+

tlpl: Apprenez comment commencer un nouveau projet Haskell. Avec en exemple le créateur de projet Haskell lui-même.

+
+

“Good Sir Knight, will you come with me to Camelot, and join us at the Round Table?”

+
+

In order to work properly with Haskell you need to initialize your environment. Typically, you need to use a cabal file, create some test for your code. Both, unit test and propositional testing (random and exhaustive up to a certain depth). You need to use git and generally hosting it on github. Also, it is recommended to use cabal sandboxes. And as bonus, an auto-update tool that recompile and retest on each file save.

+

In this article, we will create such an environment using a zsh script. Then we will write a Haskell project which does the same work as the zsh script. You will then see how to work in such an environment.

+

If you are starting to understand Haskell but consider yourself a beginner, this tutorial will show you how to make a real application using quite surprisingly a lot of features:

+
    +
  • use colorized output
  • +
  • interact with a user in command line
  • +
  • read/write files
  • +
  • kind of parse a file (in fact, simply split it)
  • +
  • use a templating system (mustache: fill a data structure, write files)
  • +
  • make a HTTP GET request then parse the JSON answer and use it
  • +
  • use random
  • +
  • create a cabal package
  • +
  • add and use non source files to a cabal package
  • +
  • Test your code (both unit testing and property testing)
  • +
+

zsh is by its nature more suitable to file manipulation. But the Haskell code is clearly more organized while quite terse for a multi-purpose language.

+

holy-project is on hackage. It can be installed with cabal update && cabal install holy-project.

+
+

I recently read this excellent article: How to Start a New Haskell Project.

+

While the article is very good, I lacked some minor informations1. Inspired by it, I created a simple script to initialize a new Haskell project. During the process I improved some things a bit.

+

If you want to use this script, the steps are:

+
    +
  1. Install Haskell
  2. +
  3. Make sure you have the latest cabal-install (at least 1.18)
  4. +
+ +
    +
  1. Download and run the script
  2. +
+ +

What does this script do that cabal init doesn’t do?

+
    +
  • Use cabal sandbox
  • +
  • It initialize git with the right .gitignore file.
  • +
  • Use tasty to organize your tests (HUnit, QuickCheck and SmallCheck).
  • +
  • Use -Wall for ghc compilation.
  • +
  • Will make references to Holy Grail
  • +
  • Search your default github username via github api.
  • +
+

zsh really?

+
+French insult +
+

Developing the script in zsh was easy. But considering its size, it is worth to rewrite it in Haskell. Furthermore, it will be a good exercise.

+

Patricide

+

In a first time, we initialize a new Haskell project with holy-haskell.sh:

+
+> ./holy-haskell.sh
+Bridgekeeper: Stop!
+Bridgekeeper: Who would cross the Bridge of Death
+Bridgekeeper: must answer me these questions three,
+Bridgekeeper: ere the other side he see.
+You: Ask me the questions, bridgekeeper, I am not afraid.
+
+Bridgekeeper: What is the name of your project?
+> Holy project
+Bridgekeeper: What is your name? (Yann Esposito (Yogsototh))
+>
+Bridgekeeper: What is your email? (Yann.Esposito@gmail.com)
+>
+Bridgekeeper: What is your github user name? (yogsototh)
+>
+Bridgekeeper: What is your project in less than ten words?
+> Start your Haskell project with cabal, git and tests.
+Initialize git
+Initialized empty Git repository in .../holy-project/.git/
+Create files
+    .gitignore
+    holy-project.cabal
+    Setup.hs
+    LICENSE (MIT)
+    test/Test.hs
+    test/HolyProject/Swallow/Test.hs
+    src/HolyProject/Swallow.hs
+    test/HolyProject/Coconut/Test.hs
+    src/HolyProject/Coconut.hs
+    src/HolyProject.hs
+    src/Main.hs
+Cabal sandboxing, install and test
+...
+  many compilations lines
+...
+Running 1 test suites...
+Test suite Tests: RUNNING...
+Test suite Tests: PASS
+Test suite logged to: dist/test/holy-project-0.1.0.0-Tests.log
+1 of 1 test suites (1 of 1 test cases) passed.
+All Tests
+  Swallow
+    swallow test:     OK
+  coconut
+    coconut:          OK
+    coconut property: OK
+      148 tests completed
+
+All 3 tests passed
+
+
+
+Bridgekeeper: What... is the air-speed velocity of an unladen swallow?
+You: What do you mean? An African or European swallow?
+Bridgekeeper: Huh? I... I don't know that.
+[the bridgekeeper is thrown over]
+Bridgekeeper: Auuuuuuuuuuuugh
+Sir Bedevere: How do you know so much about swallows?
+You: Well, you have to know these things when you're a king, you know.
+
+

The different steps are:

+
    +
  • small introduction quotes
  • +
  • ask five questions – three question sir…
  • +
  • create the directory for the project
  • +
  • init git
  • +
  • create files
  • +
  • sandbox cabal
  • +
  • cabal install and test
  • +
  • run the test directly in the terminal
  • +
  • small goodbye quotes
  • +
+

Features to note:

+
    +
  • color in the terminal
  • +
  • check some rules on the project name
  • +
  • random message if error
  • +
  • use ~/.gitconfig file in order to provide a default name and email.
  • +
  • use the github API which returns JSON to get the default github user name.
  • +
+

So, apparently nothing too difficult to achieve.

+

We should now have an initialized Haskell environment for us to work. The first thing you should do, is to go into this new directory and launch ‘./auto-update’ in some terminal. I personally use tmux on Linux or the splits in iTerm 2 on Mac OS X. Now, any modification of a source file will relaunch a compilation and a test.

+

The dialogs

+
+Bridge of Death +
+

To print the introduction text in zsh:

+ +

In the first Haskell version I don’t use colors. We see we can almost copy/paste. I just added the types.

+ +

Now let’s just add the colors using the ansi-terminal package. So we have to add ansi-terminal as a build dependency in our cabal file.

+

Edit holy-project.cabal to add it.

+
...
+build-depends:  base >=4.6 && <4.7
+                , ansi-terminal
+...
+

Now look at the modified Haskell code:

+ +

We could put this code in src/Main.hs. Declare a main function:

+ +

Make cabal install and run cabal run (or ./.cabal-sandbox/bin/holy-project). It works!

+

Five Questions – Three questions Sir!

+
+Bring out your dead! +
+

In order to ask questions, here is how we do it in shell script:

+ +

If we want to abstract things a bit, the easiest way in shell is to use a global variable2 which will get the value of the user input like this:

+ +

In Haskell we won’t need any global variable:

+ +

Now our main function might look like:

+ +

You could test it with cabal install and then ./.cabal-sandbox/bin/holy-project.

+

We will see later how to guess the answer using the .gitconfig file and the github API.

+

Using answers

+
+Castle of Aaaaarrrr???? +
+

Create the project name

+

I don’t really like the ability to use capital letter in a package name. So in shell I transform the project name like this:

+ +

In order to achieve the same result in Haskell (don’t forget to add the split package):

+ +

One important thing to note is that in zsh the transformation occurs on strings but in haskell we use list as intermediate representation:

+
zsh:
+"Holy grail" ==( ${project:gs/ /-/} )=> "Holy-grail"
+             ==( ${project:l}       )=> "holy-grail"
+
+haskell
+"Holy grail" ==( map toLower     )=> "holy grail"
+             ==( splitOneOf " -" )=> ["holy","grail"]
+             ==( intercalate "-" )=> "holy-grail"
+

Create the module name

+

The module name is a capitalized version of the project name where we remove dashes.

+ + +

The haskell version is made by hand where zsh already had a capitalize operation on string with many words. Here is the difference between the shell and haskell way (note I splitted the effect of concatMap as map and concat):

+
shell:
+"Holy-grail" ==( sed 's/-/ /g' )=> "Holy grail"
+             ==( ${(C)str}     )=> "Holy Grail"
+             ==( sed 's/ //g'  )=> "HolyGrail"
+
+haskell:
+"Holy-grail" ==( splitOneOf " -"    )=> ["Holy","grail"]
+             ==( map capitalizeWord )=> ["Holy","Grail"]
+             ==( concat             )=> "HolyGrail"
+

As the preceding example, in shell we work on strings while Haskell use temporary lists representations.

+

Check the project name

+

Also I want to be quite restrictive on the kind of project name we can give. This is why I added a check function.

+ +

Which verify the project name is not empty and use only letter, numbers and dashes:

+ +

Create the project

+
+Giant with three heads and mustaches +
+

Making a project will consists in creating files and directories whose name and content depends on the answer we had until now.

+

In shell, for each file to create, we used something like:

+ +

In Haskell, while possible, we shouldn’t put the file content in the source code. We have a relatively easy way to include external file in a cabal package. This is what we will be using.

+

Furthermore, we need a templating system to replace small part of the static file by computed values. For this task, I choose to use hastache, a Haskell implementation of Mustache templates3.

+

Add external files in a cabal project

+

Cabal provides a way to add files which are not source files to a package. You simply have to add a Data-Files: entry in the header of the cabal file:

+
data-files: scaffold/LICENSE
+            , scaffold/Setup.hs
+            , scaffold/auto-update
+            , scaffold/gitignore
+            , scaffold/interact
+            , scaffold/project.cabal
+            , scaffold/src/Main.hs
+            , scaffold/src/ModuleName.hs
+            , scaffold/src/ModuleName/Coconut.hs
+            , scaffold/src/ModuleName/Swallow.hs
+            , scaffold/test/ModuleName/Coconut/Test.hs
+            , scaffold/test/ModuleName/Swallow/Test.hs
+            , scaffold/test/Test.hs
+

Now we simply have to create our files at the specified path. Here is for example the first lines of the LICENSE file.

+
The MIT License (MIT)
+
+Copyright (c) {{year}} {{author}}
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+...
+

It will be up to our program to replace the {{year}} and {{author}} at runtime. We have to find the files. Cabal will create a module named Paths_holy_project. If we import this module we have the function genDataFileName at our disposal. Now we can read the files at runtime like this:

+ +

Create files and directories

+

A first remark is for portability purpose we shouldn’t use String for file path. For example on Windows / isn’t considered as a subdirectory character. To resolve this problem we will use FilePath:

+ +

Use Hastache

+

In order to use hastache we can either create a context manually or use generics to create a context from a record. This is the last option we will show here. So in a first time, we need to import some modules and declare a record containing all necessary informations to create our project.

+ + +

Once we have declared this, we should populate our Project record with the data provided by the user. So our main function should look like:

+ +

Finally we could use hastache this way:

+ +

We use external files in mustache format. We ask question to our user to fill a data structure. We use this data structure to create a context. Hastache use this context with the external files to create the project files.

+

Git and Cabal

+
+Tim +
+

We need to initialize git and cabal. For this we simply call external command with the system function.

+ +

Ameliorations

+

Our job is almost finished. Now, we only need to add some nice feature to make the application more enjoyable.

+

Better error message

+
+Rabbit +
+

The first one would be to add a better error message.

+ +

And also update where this can be called

+ +

Use .gitconfig

+

We want to retrieve the ~/.gitconfig file content and see if it contains a name and email information. We will need to access to the HOME environment variable. Also, as we use bytestring package for hastache, let’s take advantage of this library.

+ +

We could note I changed the ask function slightly to take a maybe parameter.

+ +

Concerning the parsing of .gitconfig, it is quite minimalist.

+ +

We could notice, getNameAndMail doesn’t read the full file and stop at the first occurrence of name and mail.

+

Use the github API

+
+Coconut and Swallow +
+

The task seems relatively easy, but we’ll see there will be some complexity hidden. Make a request on https://api.github.com/search/users?q=<email>. Parse the JSON and get the login field of the first item.

+

So the first problem to handle is to connect an URL. For this we will use the http-conduit package.

+

Generally, for simple request, we should use:

+ +

But, after some research, I discovered we must declare an User-Agent in the HTTP header to be accepted by the github API. So we have to change the HTTP Header, and our code became slightly more complex:

+ +

So now, we have a String containing a JSON representation. In javascript we would have used login=JSON.parse(body).items[0].login. How does Haskell will handle it (knowing the J in JSON is for Javascript)?

+

First we will need to add the lens-aeson package and use it that way:

+ +

It looks ugly, but it’s terse. In fact each function (^?), key and nth has some great mathematical properties and everything is type safe. Unfortunately I had to make my own jsonValueToString. I hope I simply missed a simpler existing function.

+

You can read this article on lens-aeson and prisms to know more.

+

Concurrency

+
+Priests +
+

We now have all the feature provided by the original zsh script shell. But here is a good occasion to use some Haskell great feature.

+

We will launch the API request sooner and in parallel to minimize our wait time:

+
import Control.Concurrent
+...
+main :: IO ()
+main = do
+    intro
+    gitconfig <- safeReadGitConfig
+    let (name,email) = getNameAndMail gitconfig
+    earlyhint <- newEmptyMVar
+    maybe   (putMVar earlyhint Nothing) -- if no email found put Nothing
+            (\hintmail -> do  -- in the other case request the github API
+                forkIO (putMVar earlyhint =<< getGHUser hintmail)
+                return ())
+            email
+    project <- ask "project name" Nothing
+    ioassert (checkProjectName project)
+             "Use only letters, numbers, spaces and dashes please"
+    let projectname = projectNameFromString project
+        modulename  = capitalize project
+    in_author       <- ask "name" name
+    in_email        <- ask "email" email
+    ghUserHint      <- if maybe "" id email /= in_email
+                            then getGHUser in_email
+                            else takeMVar earlyhint
+    in_ghaccount    <- ask "github account" ghUserHint
+    in_synopsis     <- ask "project in less than a dozen word?" Nothing
+    current_year    <- getCurrentYear
+    createProject $ Project projectname modulename in_author in_email
+                            in_ghaccount in_synopsis current_year
+    end
+

While it might feel a bit confusing, it is in fact quite simple.

+
    +
  1. declare an MVar. Mainly a variable which either is empty or contains something.
  2. +
  3. If we didn’t found any email hint, put Nothing in the MVar.
  4. +
  5. If we have an email hint, ask on the github API in a new process and once finished put the result in the MVar.
  6. +
  7. If the user enter an email different from the hint email, then just request the github api now.
  8. +
  9. If the user enter the same email, then wait for the MVar to be filled and ask the next question with the result.
  10. +
+

If you have a github account and had set correctly your .gitconfig, you might not even wait.

+

Project Structure

+

We have a working product. But, I don’t consider our job finished. The code is about 335 lines.

+

Considering that we:

+
    +
  • have 29 lines of import and 52 lines of comments (rest 255 lines)
  • +
  • ask questions
  • +
  • use a templating system to generate files
  • +
  • call an asynchronous HTTP request
  • +
  • parse JSON
  • +
  • parse .gitconfig
  • +
  • use colored output
  • +
+

This is quite few.

+

Modularizing

+
+The Black Knight +
+

For short programs it is not obvious to split them into different modules. But my personal preference is to split it anyway.

+

First we put all content of src/Main.hs in src/HolyProject.hs. We rename the main function by holyStarter. And our src/Main.hs should contains:

+ +

Of course you have to remember to rename the module of src/HolyProject.hs. I separated all functions in different submodules:

+
    +
  • HolyProject.GitConfig +
      +
    • getNameAndMailFromGitConfig: retrieve name an email from .gitconfig file
    • +
  • +
  • HolyProject.GithubAPI +
      +
    • searchGHUser: retrieve github user name using github API.
    • +
  • +
  • HolyProject.MontyPython +
      +
    • bk: bridge keeper speaks
    • +
    • you: you speak
    • +
    • ask: Ask a question and wait for an answer
    • +
  • +
  • HolyProject.StringUtils: String helper functions +
      +
    • projectNameFromString
    • +
    • capitalize
    • +
    • checkProjectName
    • +
  • +
+

The HolyProject.hs file contains mostly the code that ask questions, show errors and copy files using hastache.

+

One of the benefits in modularizing the code is that our main code is clearer. Some functions are declared only in a module and are not exported. This help us hide technical details. For example, the modification of the HTTP header to use the github API.

+

Documenting

+
+The Holy Grenade +
+

We didn’t take much advantage of the project structure yet. A first thing is to generate some documentation. Before most function I added comment starting with -- |. These comment will be used by haddock to create a documentation. First, you need to install haddock manually.

+ +

Be sure to have haddock in your PATH. You could for example add it like this:

+ +

And if you are at the root of your project you’ll get it. And now just launch:

+ +

And magically, you’ll have a documentation in dist/doc/html/holy-project/index.html.

+

Tests

+

While the Haskell static typing is quite efficient to prevent entire classes of bugs, Haskell doesn’t discard the need to test to minimize the number of bugs.

+

Unit Testing with HUnit

+
+A Witch! A Witch! +
+

It is generally said to test we should use unit testing for code in IO and QuickCheck or SmallCheck for pure code.

+

A unit test example on pure code is in the file test/HolyProject/Swallow/Test.hs:

+ +

Note swallow is (++). We group tests by group. Each group can contain some test suite. Here we have a test suite with only one test. The (@=?) verify the equality between its two parameters.

+

So now, we could safely delete the directory test/HolyProject/Swallow and the file src/HolyProject/Swallow.hs. And we are ready to make our own real world unit test. We will first test the module HolyProject.GithubAPI. Let’s create a file test/HolyProject/GithubAPI/Test.hs with the following content:

+ +

You have to modify your cabal file. More precisely, you have to add HolyProject.GithubAPI in the exposed modules of the library secion). You also have to update the test/Test.hs file to use GithubAPI instead of Swallow.

+

So we have our example of unit testing using IO. We search the github nickname for some people I know and we verify github continue to give the same answer as expected.

+

Property Testing with SmallCheck and QuickCheck

+
+My name is Zoot. Just Zoot +
+

When it comes to pure code, a very good method is to use QuickCheck and SmallCheck. SmallCheck will verify all cases up to some depth about some property. While QuickCheck will verify some random cases.

+

As this kind of verification of property is mostly doable on pure code, we will test the StringUtils module.

+

So don’t forget to declare HolyProject.StringUtils in the exposed modules in the library section of your cabal file. Remove all references to the Coconut module.

+

Modify the test/Test.hs to remove all references about Coconut. Create a test/HolyProject/StringUtils/Test.hs file containing:

+ +

The result is here:

+
+All Tests
+  StringUtils
+    SC projectNameFromString idempotent: OK
+      206 tests completed
+    SC capitalize idempotent:            OK
+      1237 tests completed
+    QC projectNameFromString idempotent: FAIL
+      *** Failed! Falsifiable (after 19 tests and 5 shrinks):
+      "a a"
+      Use --quickcheck-replay '18 913813783 2147483380' to reproduce.
+  GithubAPI
+    Yann:                                OK
+    Jasper:                              OK
+
+1 out of 5 tests failed
+
+

The test fail, but this is not an error. Our capitalize function shouldn’t be idempotent. I simply added this test to show what occurs when a test fail. If you want to look more closely to the error you could do this:

+ +

It is important to use ./interact instead of ghci. Because we need to tell ghci how to found the package installed.

+

Apparently, SmallCheck didn’t found any counter example. I don’t know how it generates Strings and using deeper search is really long.

+

Conclusion

+
+Rabbit +
+

Congratulation!

+

Now you could start programming in Haskell and publish your own cabal package.

+
+
+
    +
  1. For example, you have to install the test libraries manually to use cabal test.

  2. +
  3. There is no easy way to do something like name=$(ask name). Simply because $(ask name) run in another process which doesn’t get access to the standard input

  4. +
  5. Having a good level of power in templates is very difficult. imho Mustache has made the best compromise.

  6. +
+
]]>
+
+ + Parsec Presentation + + http://yannesposito.com/Scratch/fr/blog/Parsec-Presentation/index.html + 2013-10-09T00:00:00Z + 2013-10-09T00:00:00Z + AST
+

+
+

tlpl: Une introduction rapide à Parsec. Un parser en Haskell.

+
+
    +
  • The html presentation is here.
  • +
+
+() () () () () ( +) ( +

) () () () () () () () () ()

+
+ +
+
+

+Parsec +

+by Yann Esposito + + +
+
+
+

+Parsing +

+

+Latin pars (ōrātiōnis), meaning part (of speech). +

+
    +
  • +analysing a string of symbols +
  • +
  • +formal grammar. +
  • +
+
+
+

+Parsing in Programming Languages +

+

+Complexity: +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+Method + +Typical Example + +Output Data Structure +
+Splitting + +CSV + +Array, Map +
+Regexp + +email + +
    +
  • Fixed Layout Tree +
+Parser + +Programming language + +
    +
  • Most Data Structure +
+
+
+

+Parser & culture +

+

+In Haskell Parser are really easy to use. +

+

+Generally: +

+
    +
  • +In most languages: split then regexp then parse +
  • +
  • +In Haskell: split then parse +
  • +
+
+
+

+Parsing Example +

+

+From String: +

+
(1+3)*(1+5+9)
+

+To data structure: +

+

+AST
+

+
+
+

+Parsec +

+
+

+Parsec lets you construct parsers by combining high-order Combinators to create larger expressions. +

+

+Combinator parsers are written and used within the same programming language as the rest of the program. +

+

+The parsers are first-class citizens of the languages […]" +

+

+Haskell Wiki +

+
+
+
+

+Parser Libraries +

+

+In reality there are many choices: +

+ + + + + + + + + + + + + + + +
+attoparsec + +fast +
+Bytestring-lexing + +fast +
+Parsec 3 + +powerful, nice error reporting +
+
+
+

+Haskell Remarks (1) +

+

+spaces are meaningful +

+
f x   -- ⇔ f(x) in C-like languages
+f x y -- ⇔ f(x,y)
+
+
+

+Haskell Remarks (2) +

+

+Don’t mind strange operators (<*>, <$>).
Consider them like separators, typically commas.
They are just here to deal with types. +

+

+Informally: +

+
toto <$> x <*> y <*> z
+-- ⇔ toto x y z
+-- ⇔ toto(x,y,z) in C-like languages
+
+
+

+Minimal Parsec Examples +

+
whitespaces = many (oneOf "\t ")
+number = many1 digit
+symbol = oneOf "!#$%&|*+-/:<=>?@^_~"
+
+" \t " – whitespaces on " \t " "" – whitespaces on “32” “32” – number on “32”
+
+
+– number on " ]]>
+
+ + Rational Web Framework Choice + + http://yannesposito.com/Scratch/fr/blog/Rational-Web-Framework-Choice/index.html + 2013-08-06T00:00:00Z + 2013-08-06T00:00:00Z + +Main image + +
+

tlpl: Comment déterminer de la façon la plus rationnelle possible le meilleur framework work relativement à vos besoins. Cliquez ici pour aller au résultats. Cet article n’est disponible qu’en anglais.

+
+

This is it.
+You’ve got the next big idea.
+You just need to make a very simple web application.

+

It sounds easy! You just need to choose a good modern web framework, when suddenly:

+
+[Choice Paralysis][choice_paralysis] +
+Choice Paralysis +
+
+

After your brain stack overflowed, you decide to use a very simple methodology. Answer two questions:

+

Which language am I familiar with?
+What is the most popular web framework for this language?

+

Great! This is it.

+

But, you continually hear this little voice.

+
+

“You didn’t made a bad choice, yes. But …
+you hadn’t made the best either.”

+
+

This article try to determine in the most objective and rational way the best(s) web framework(s) depending on your needs. To reach this goal, I will provide a decision tool in the result section.

+

I will use the following methodology:

+

Methodology

+
    +
  1. Model how to make choice +
      +
    1. choose important parameters
    2. +
    3. organize (hierarchize) them
    4. +
    5. write down an objective chooser
    6. +
  2. +
  3. Grab objective quantified informations about web frameworks relatively to choosen parameters
  4. +
  5. Sanitize your data in order to handle imprecisions, lack of informations…
  6. +
  7. Apply the model of choice to your informations
  8. +
+
+

☞ Important Note
+I am far from happy to the actual result. There are a lot of biases, for example in the choice of the parameters. The same can be said about the data I gathered. I am using very imprecise informations. But, as far as I know, this is the only article which use many different parameters to help you choose a web framework.

+

This is why I made a very flexible decision tool:

+

Decision tool.

+
+

Model

+

Here are the important features (properties/parameters) I selected to make the choice:

+
    +
  1. Popularity, which correlate with: +
      +
    • number of tested libraries
    • +
    • facility to find learning material
    • +
    • ability to find another developer to work with
    • +
  2. +
  3. Efficiency, which is generally correlated to: +
      +
    • how much processing power you’ll need per user
    • +
    • maintenance price per user
    • +
    • how long the user will wait to see/update data
    • +
  4. +
  5. Expressiveness, which is generally correlated to: +
      +
    • faster development
    • +
    • flexibility, adaptability
    • +
  6. +
  7. Robustness, which correlate with: +
      +
    • security
    • +
    • fewer bugs
    • +
  8. +
+

Each feature is quite important and mostly independant from each other. I tried to embrace most important topics concerning web frameworks with these four properties. I am fully concious some people might lack another important feature. Nonetheless the methodology used here can be easily replicated. If you lack an important property add it at will and use this choice method.

+

Also each feature is very hard to measure with precision. This is why we will only focus on order of magnitude.

+

For each property a framework could have one of the six possible values: Excellent, Very Good, Good, Medium, Bad or Very Bad

+

So how to make a decision model from these informations?

+

One of the most versatile method is to give a weight for each cluster value. And to select the framework maximizing this score:

+
score(framework) = efficiency + robustness + expressiveness + popularity
+
+

For example:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Expressiveness1071-∞-∞-∞
Popularity554321
Efficiency1086421
Robustness1086421
+

Using this weighted table, that means:

+
    +
  • we discard the three least expressive clusters.
  • +
  • We don’t make any difference between excellent and very good in popularity.
  • +
  • Concerning efficient framework in excellent cluster will have 2 more points than the “very good” cluster.
  • +
+

So for each framework we compute its score relatively to a weighted table. And we select the best(s).

+

Example: Using this hypothetic framework and the preceeding table.

+ + + + + + + + + + + + + + + + + + + +
ExpressivenessPopularityEfficiencyRobustness
yogExcellentVery BadMediumVery Good
+
score(yog) = 10 + 0 + 4 + 8 = 22
+
+

Most needs should be expressed by such a weighted table. In the result section, we will discuss this further.

+

It is now time to try to get these measures.

+

Objective measures

+

None of the four properties I choosen can be measured with perfect precision. But we could get the order of magnitude for each.

+

I tried to focus on the framework only. But it is often easier to start by studying the language first.

+

For example, I have datas about popularity by language and I also have different datas concerning popularity by framework. Even if I use only the framework focused datas in my final decision model, it seemed important to me to discuss about the datas for the languages. The goal is to provide a tool to help decision not to give a decision for you.

+

Popularity

+

RedMonk Programming Language Rankings (January 2013) provide an apparent good measure of popularity. While not perfect the current measure feel mostly right. They create an image using stack overflow and github data. Vertical correspond to the number of questions on stackoverflow. Horizontal correspond to the number of projects on github.

+

If you look at the image, your eye can see about four clusters. The 1 cluster correspond to mainstream languages:

+
+Mainstream Languages Cluster from [RedMonk][redmonk] +
+Mainstream Languages Cluster from RedMonk +
+
+

Most developer know at least one of these language.

+

The second cluster is quite bigger. It seems to correspond to languages with a solid community behind them.

+
+Second tier languages from [RedMonk][redmonk] +
+Second tier languages from RedMonk +
+
+

I don’t get into detail, but you could also see third and fourth tier popular languages.

+

So:

+

Mainstream: JavaScript, Java, PHP, Python, Ruby, C#, C++, C, Objective-C, Perl, Shell

+

Good: Scala, Haskell, Visual Basic, Assembly, R, Matlab, ASP, ActionScript, Coffeescript, Groovy, Clojure, Lua, Prolog

+

Medium: Erlang, Go, Delphi, D, Racket, Scheme, ColdFusion, F#, FORTRAN, Arduino, Tcl, Ocaml

+

Bad: third tier Very Bad: fourth tier

+

I don’t thing I could find easily web frameworks for third or fourth tier languages.

+

For now, I only talked about language popularity. But what about framework popularity? I made a test using number of question on stackoverflow only. Then by dividing by two for each 6 cluster:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework#nb%
ExcellentRubyRails176208100%
Very GoodPythonDjango57385<50%
JavaServlet54139
JavaSpring31641
Node.jsnode.js27243
PHPCodeigniter21503
GroovyGrails20222
GoodRubySinatra8631<25%
PythonFlask7062
PHPLaravel6982
PHPKohana5959
Node.jsExpress5009
MediumPHPCake4554<13%
C♯ServiceStack3838
ScalaPlay3823
JavaWicket3819
DartDart3753
PHPSlim3361
PythonTornado3321
ScalaLift2844
GoGo2689
BadJavaTapestry1197<6%
C♯aspnet1000
HaskellYesod889
PHPSilex750
PHPLithium732
C♯nancy705
Very badJavaGrizzly622<3%
ErlangCowboy568
PerlDancer496
PHPSymphony2491
GoRevel459
ClojureCompojure391
PerlMojolicious376
ScalaScalatra349
ScalaFinagle336
PHPPhalcon299
jsRingo299
JavaGemini276
HaskellSnap263
PerlPlack257
ErlangElli230
JavaDropwizard188
PHPYaf146
JavaPlay1133
Node.jsHapi131
JavaVertx60
ScalaUnfiltered42
Conion18
Clojurehttp-kit17
PerlKelp16
PHPMicromvc13
LuaOpenresty8
C++cpoll-cppsp5
ClojureLuminus3
PHPPhreeze1
+

As we can see, our framework popularity indicator can be quite different from its language popularity. For now I didn’t found a nice way to merge the results from RedMonk with these one. So I’ll use these unperfect one. Hopefully the order of magninute is mostly correct for most framework.

+

Efficiency

+

Another objective measure is efficiency. We all know benchmarks are all flawed. But they are the only indicators concerning efficiency we have.

+

I used the benchmark from benchmarksgame. Mainly, there are five clusters:

+ + + + + + + + + + + + + + + + + + + + + + + +
1x→2xC, C++
2x→3xJava 7, Scala, OCamL, Haskell, Go, Common LISP
3x→10xC♯, Clojure, Racket, Dart
10x→30xErlang
30x→PHP, Python, Perl, Ruby, JRuby
+

Remarks concerning some very slow languages:

+
    +
  • PHP ; huge variations, can be about 1.5x C speed in best case.
  • +
  • Python ; huge variations, can be about 1.5x C speed in best case
  • +
  • Perl ; Can be about 3x C speed in best case
  • +
  • Ruby, JRuby ; mostly very slow.
  • +
+

This is a first approach. The speed of the language for basic benchmarks. But, here we are interrested in web programming. Fortunately techempower has made some tests focused on most web frameworks:

+

Web framework benchmarks.

+

These benchmark doesn’t fit well with our needs. The values are certainly quite imprecise to your real usage. The goal is just to get an order of magnitude for each framework. Another problem is the high number of informations.

+

As always, we should remember these informations are also imprecise. So I simply made some classes of efficiency.

+

Remark: I separated the clusters by using power of 2 relatively to the fastest.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework#nbslowness
ExcellentC++cpoll-cppsp114,711
Javgemini105,204
Luaopenresty93,882
Javservlet90,580
C++cpoll-pool89,167
Gogo76,024
Scafinagle68,413
Gorevel66,990
Javrest-express63,209
Very GoodJavwicket48,772>2×
Scascalatra48,594
Cljhttp-kit42,703
Javspring36,643>3×
PHPphp36,605
Javtapestry35,032
Cljcompojure32,088
JSringo31,962
Javdropwizard31,514
Cljluminus30,672
GoodScaplay-slick29,950>4×
Scaunfiltered29,782
Erlelli28,862
Javvertx28,075
JSnodejs27,598
Erlcowboy24,669
Conion23,649
Hklyesod23,304
JSexpress22,856>5×
Scaplay-scala22,372
Jav grizzly-jersey20,550
Pytornado20,372>6×
PHPphalcon18,481
Grvgrails18,467
Prlplack16,647>7×
PHPyaf14,388
MediumJShapi11,235>10×
Javplay19,979
Hklsnap9,196
Prlkelp8,250
Pyflask8,167
Javplay-java7,905
Jav play-java-jpa7,846
PHPmicromvc7,387
Prldancer5,040>20×
Prlmojolicious4,371
JSringo-conv4,249
Pydjango4,026
PHPcodeigniter3,809>30×
BadRbyrails3,445
Scalift3,311
PHPslim3,112
PHPkohana2,378>40×
PHPsilex2,364
Very BadPHPlaravel1,639>60×
PHPphreeze1,410
PHPlithium1,410
PHPfuel1,410
PHPcake1,287>80×
PHPsymfony2879>100×
C#aspnet-mvc871
Rbysinatra561>200×
C#servicestack51
Dardart0
C#nancy0
Prlweb-simple0
+

These are manually made clusters. But you get the idea. Certainly, some framework could jump between two different clusters. So this is something to remember. But as always, the order of magnitude is certainly mostly right.

+

Expressiveness

+

Now, how to objectively measure expressiveness?

+

RedMonk had a very good idea to find an objective (while imprecise) measure of each language expressiveness. Read this article for details.

+

After filtering languages suitable for web development, we end up with some clusters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguages
ExcellentCoffeescript, Clojure, Haskell
Very GoodRacket, Groovy, R, Scala, OCamL, F♯, Erlang, Lisp, Go
MediumPerl, Python, Objective-C, Scheme, Tcl, Ruby
BadLua, Fortran (free-format), PHP, Java, C++, C♯
Very BadAssembly, C, Javascript,
+

Unfortunately there is no information about dart. So I simply give a very fast look at the syntax. As it looked a lot like javascript and js is quite low. I decided to put it close to java.

+

Also an important remark, javascript score very badly here while coffeescript (compiling to js) score “excellent”. So if you intend to use a javascript framework but only with coffescript that should change substantially the score. As I don’t believe it is the standard. Javascript oriented framework score very badly regarding expressiveness.

+
+Click here to show/hide the table for frameworks +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework
ExcellentCljluminus
Cljhttp-kit
Cljcompojure
Hklsnap
Hklyesod
Very GoodErlelli
Erlcowboy
Gogo
Gorevel
Grvgrails
Scalift
Scafinagle
Scascalatra
Scaplay-scala
Scaplay-slick
Scaunfiltered
MediumPrlkelp
Prlplack
Prldancer
Prlweb-simple
Prlmojolicious
Pyflask
Pydjango
Pytornado
Rbyrails
Rbysinatra
BadC#nancy
C#aspnet-mvc
C#servicestack
C++cpoll-pool
C++cpoll-cppsp
Dardart
Javplay1
Javvertx
Javgemini
Javspring
Javwicket
Javservlet
Javtapestry
Javplay-java
Javdropwizard
Javrest-express
Javplay-java-jpa
Javgrizzly-jersey
Luaopenresty
PHPphp
PHPyaf
PHPcake
PHPfuel
PHPslim
PHPsilex
PHPkohana
PHPlaravel
PHPlithium
PHPphalcon
PHPphreeze
PHPmicromvc
PHPsymfony2
PHPcodeigniter
Very BadConion
JShapi
JSringo
JSnodejs
JSexpress
JSringo-conv
+
+

Robustness

+

I couldn’t find any complete study to give the number of bug relatively to each framework/language.

+

But one thing I saw from experience is the more powerful the type system the safest your application is. While the type system doesn’t remove completely the need to test your application a very good type system tend to remove complete classes of bug.

+

Typically, not using pointer help to reduce the number of bugs due to bad references. Also, using a garbage collector, reduce greatly the probability to access unallocated space.

+
+Static Type Properties from [James IRY Blog][typesanalysis] +
+Static Type Properties from James IRY Blog +
+
+

From my point of view, robustness is mostly identical to safety.

+

Here are the clusters:

+ + + + + + + + + + + + + + + + + + + +
ExcellentHaskell, Scheme, Erlang
Very GoodScala, Java, Clojure
GoodRuby, Python, Groovy, javascript, PHP
MediumC++, C#, Perl, Objective-C, Go, C
+

So applying this to frameworks gives the following clusters:

+
+Click here to show/hide the table for frameworks +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterLanguageFramework
ExcellentErlelli
Erlcowboy
Hklsnap
Hklyesod
Very GoodCljluminus
Cljhttp-kit
Cljcompojure
Javplay1
Javvertx
Javgemini
Javspring
Javwicket
Javservlet
Javtapestry
Javplay-java
Javdropwizard
Javrest-express
Javplay-java-jpa
Javgrizzly-jersey
Scalift
Scafinagle
Scascalatra
Scaplay-scala
Scaplay-slick
Scaunfiltered
GoodGrvgrails
JShapi
JSringo
JSnodejs
JSexpress
JSringo-conv
Luaopenresty
PHPphp
PHPyaf
PHPcake
PHPfuel
PHPslim
PHPsilex
PHPkohana
PHPlaravel
PHPlithium
PHPphalcon
PHPphreeze
PHPmicromvc
PHPsymfony2
PHPcodeigniter
Pyflask
Pydjango
Pytornado
Rbyrails
Rbysinatra
MediumConion
C#nancy
C#aspnet-mvc
C#servicestack
C++cpoll-pool
C++cpoll-cppsp
Dardart
Gogo
Gorevel
Prlkelp
Prlplack
Prldancer
Prlweb-simple
Prlmojolicious
+
+

The result

+

For the result I initialized the table with my own needs.

+

And I am quite happy it confirms my current choice. I sware I didn’t given yesod any bonus point. I tried to be the most objective and factual as possible.

+

Now, it is up to you to enter your preferences.

+

On each line you could change how important a feature is for you. From essential to unsignificant. Of course you could change the matrix at will.

+

I just show a top 10 frameworks. In order to give a more understandable measure I provide the log of the score.

+ + + + + + + + + + + + + + + + + + + + + + + +
+ +Excellent + +Very good + +Good + +Medium + +Bad + +Very bad + +Importance +
+Expressiveness +
+Popularity +
+Efficiency +
+Robustness +
+
+Click to force refresh +
+
+ +
+ + +

I didn’t had the courage in explaining in what the scoring system is good. Mostly, if you use product instead of sum for the score you could use power of e for the values in the matrix. And you could see the matrix as a probability matrix (each line sum to 1). Which provide a slighly better intuition on whats going on.

+

Remember only that values are exponential. Do not double an already big value for example the effect would be extreme.

+

Conclusion

+

All of this is based as most as I could on objective data. The choice method seems both rather rational and classical. It is now up to you to edit the score matrix to set your needs.

+

I know that in the current state there are many flaws. But it is a first system to help make a choice rationally.

+

I encourage you to go further if you are not satisfied by my method.

+

The source code for the matrix shouldn’t be too hard to read. Just read the source of this webpage. You could change the positionning of some frameworks if you believe I made some mistake by placing them in some bad clusters.

+

So I hope this tool will help you in making your life easier.

]]>
+
+ + Hakyll setup + + http://yannesposito.com/Scratch/fr/blog/Hakyll-setup/index.html + 2013-03-16T00:00:00Z + 2013-03-16T00:00:00Z + +Main image + +
+

tlpl: Comment j’utilise hakyll. Abréviations, corrections typographiques, multi-language, utilisation d’index.html, etc…

+
+

Ce site web est fait avec Hakyll.

+

Hakyll peut être vu comme un cms minimaliste. D’une façon plus générale, il s’agit d’une bibliothèque qui facilite la création automatique de fichiers.

+

D’un point de vue utilisateur voici comment j’écris mes articles :

+
    +
  1. J’ouvre un éditeur de texte (vim dans mon cas). J’édite un fichier markdow qui ressemble à ça :
  2. +
+ +
    +
  1. J’ouvre mon navigateur et je rafraichis de temps en temps pour voir les changements.
  2. +
  3. Une fois satisfait, je lance un script minimal qui fait grosso modo un simple git push. Mon blog est hébergé sur github.
  4. +
+

A ne pas y regarder de trop près, on peut réduire le rôle d’Hakyll à :

+
+

Créer (resp. mettre à jour) un fichier html lorsque je crée (resp. modifie) un fichier markdown.

+
+

Bien que cela semble facile, il y a de nombreux détails cachés :

+
    +
  • Ajouter des métadatas comme des mots clés
  • +
  • Créer un page archive qui contient la liste de tous les articles
  • +
  • Gérer les fichier statiques
  • +
  • Créer un flux rss
  • +
  • Filtrer le contenu
  • +
  • Gérer les dépendances
  • +
+

Le travail d’Hakyll est de vous aider avec tout ça. Commençons par expliquer les concepts basiques.

+

Les concepts et la syntaxe

+
+Overview +
+

Pour chaque fichier que vous créer, il faut fournir :

+
    +
  • un chemin de destination
  • +
  • une liste de filtres du contenu
  • +
+

Commençons par le cas le plus simple ; les fichiers statiques (images, fontes, etc…) Généralement, vous avec un répertoire source (ici le répertoire courant) et une répertoire destination _site.

+

Le code Hakyll est :

+ +

Ce programme va copier static/foo.jpg dans _site/static/foo.jpg. C’est un peu lourd pour un simple cp. Maintenant comment faire pour transformer automatiquement un fichier markdown dans le bon html?

+ +

Si vous créez un fichier posts/toto.md, cela créera un fichier _site/posts/toto.html.

+

Si le fichier posts/foo.md contient

+ +

le fichier _site/posts/foo.html, contiendra

+ +

Mais horreur ! _site/posts/cthulhu.html n’est pas un html complet. Il ne possède ni header, ni footer, etc… C’est ici que nous utilisons des templates. J’ajoute une nouvelle directive dans le bloc “compile”.

+ +

Maintenant si templates/posts.html contient:

+ +

Maintenant notre ctuhlhu.html contient

+ +

C’est facile. Mais il reste un problème à résoudre. Comment pouvons-nous changer le titre ? Ou par exemple, ajouter des mots clés ?

+

La solution est d’utiliser les Contexts. Pour cela, nous devrons ajouter des metadonnées à notre markdown1.

+ +

Et modifier légèrement notre template :

+ +

Super facile!

+

La suite de l’article est en Anglais. Je la traduirai volontier si suffisamment de personnes me le demande gentillement.

+

Real customization

+

Now that we understand the basic functionality. How to:

+
    +
  • use SASS?
  • +
  • add keywords?
  • +
  • simplify url?
  • +
  • create an archive page?
  • +
  • create an rss feed?
  • +
  • filter the content?
  • +
  • add abbreviations support?
  • +
  • manage two languages?
  • +
+

Use SASS

+

That’s easy. Simply call the executable using unixFilter. Of course you’ll have to install SASS (gem install sass). And we also use compressCss to gain some space.

+ +

Add keywords

+

In order to help to reference your website on the web, it is nice to add some keywords as meta datas to your html page.

+ +

In order to add keywords, we could not directly use the markdown metadatas. Because, without any, there should be any meta tag in the html.

+

An easy answer is to create a Context that will contains the meta tag.

+ +

Then we pass this Context to the loadAndApplyTemplate function:

+ +
+

☞ Here are the imports I use for this tutorial.

+ +
+

Simplify url

+

What I mean is to use url of the form:

+
http://domain.name/post/title-of-the-post/
+

I prefer this than having to add file with .html extension. We have to change the default Hakyll route behavior. We create another function niceRoute.

+ +

Not too difficult. But! There might be a problem. What if there is a foo/index.html link instead of a clean foo/ in some content?

+

Very simple, we simply remove all /index.html to all our links.

+ +

And we apply this filter at the end of our compilation

+ +

Create an archive page

+

Creating an archive start to be difficult. There is an example in the default Hakyll example. Unfortunately, it assumes all posts prefix their name with a date like in 2013-03-20-My-New-Post.md.

+

I migrated from an older blog and didn’t want to change my url. Also I prefer not to use any filename convention. Therefore, I add the date information in the metadata published. And the solution is here:

+ +

Where templates/archive.html contains

+ +

And base.html is a standard template (simpler than post.html).

+

archiveCtx provide a context containing an html representation of a list of posts in the metadata named posts. It will be used in the templates/archive.html file with $posts$.

+ +

postList returns an html representation of a list of posts given an Item sort function. The representation will apply a minimal template on all posts. Then it concatenate all the results. The template is post-item.html:

+ +

Here is how it is done:

+ +

createdFirst sort a list of item and put it inside Compiler context. We need to be in the Compiler context to access metadatas.

+ +

It wasn’t so easy. But it works pretty well.

+

Create an rss feed

+

To create an rss feed, we have to:

+
    +
  • select only the lasts posts.
  • +
  • generate partially rendered posts (no css, js, etc…)
  • +
+

We could then render the posts twice. One for html rendering and another time for rss. Remark we need to generate the rss version to create the html one.

+

One of the great feature of Hakyll is to be able to save snapshots. Here is how:

+ +

Now for each post there is a snapshot named “content” associated. The snapshots are created before applying a template and after applying pandoc. Furthermore feed don’t need a source markdown file. Then we create a new file from no one. Instead of using match, we use create:

+ +

The feedConfiguration contains some general informations about the feed.

+ +

Great idea certainly steal from nanoc (my previous blog engine)!

+

Filter the content

+

As I just said, nanoc was my preceding blog engine. It is written in Ruby and as Hakyll, it is quite awesome. And one thing Ruby does more naturally than Haskell is regular expressions. I had a lot of filters in nanoc. I lost some because I don’t use them much. But I wanted to keep some. Generally, filtering the content is just a way to apply to the body a function of type String -> String.

+

Also we generally want prefilters (to filter the markdown) and postfilters (to filter the html after the pandoc compilation).

+

Here is how I do it:

+ +

Where

+ +

Now we have a simple way to filter the content. Let’s augment the markdown ability.

+

Add abbreviations support

+

Comparing to LaTeX, a very annoying markdown limitation is the lack of abbreviations.

+

Fortunately we can filter our content. And here is the filter I use:

+ +

It will search for all string starting by ‘%’ and it will search in the Map if there is a corresponding abbreviation. If there is one, we replace the content. Otherwise we do nothing.

+

Do you really believe I type

+ +

each time I write LaTeX?

+

Manage two languages

+

Generally I write my post in English and French. And this is more difficult than it appears. For example, I need to filter the language in order to get the right list of posts. I also use some words in the templates and I want them to be translated.

+ +

First I create a Map containing all translations.

+ +

Then I create a context for all key:

+ +

Conclusion

+

The full code is here. And except from the main file, I use literate Haskell. This way the code should be easier to understand.

+

If you want to know why I switched from nanoc:

+

My preceding nanoc website was a bit too messy. So much in fact, that the dependency system recompiled the entire website for any change.

+

So I had to do something about it. I had two choices:

+
    +
  1. Correct my old code (in Ruby)
  2. +
  3. Duplicate the core functionalities with Hakyll (in Haskell)
  4. +
+

I added too much functionalities in my nanoc system. Starting from scratch (almost) remove efficiently a lot of unused crap.

+

So far I am very happy with the switch. A complete build is about 4x faster. I didn’t broke the dependency system this time. As soon as I modify and save the markdown source, I can reload the page in the browser.

+

I removed a lot of feature thought. Some of them will be difficult to achieve with Hakyll. A typical example:

+

In nanoc I could take a file like this as source:

+ +

And it will create a file foo.hs which could then be downloaded.

+ +
+
+
    +
  1. Nous pouvons aussi ajouter ces métadonnées dans un fichier externe (toto.md.metadata).

  2. +
+
]]>
+
+ + Être correct avec les boutons share + + http://yannesposito.com/Scratch/fr/blog/Social-link-the-right-way/index.html + 2013-03-14T00:00:00Z + 2013-03-14T00:00:00Z + +Main image + + +

The problem

+

Ever been on a website and want to tweet about it? Fortunately, the website might have a button to help you. But do you really know what this button do?

+

The “Like”, “Tweet” and “+1” buttons will call a javascript. It will get access to your cookies. It helps the provider of the button to know who you are.

+

In plain English, the “+1” button will inform Google you are visiting the website, even if you don’t click on “+1”. The same is true for the “like” button for facebook and the “tweet this” button for twitter.

+

The problem is not only a privacy issue. In fact (sadly imho) this isn’t an issue for most people. These button consume computer ressources. Far more than a simple link. It thus slow down a bit the computer and consume energy. These button could also slow down the rendering of your web page.

+

Another aspect is their design. Their look and feel is mostly imposed by the provider.

+

The most problematic aspect in my opinion is to use a third party js on your website. What if tomorrow twitter update their tweet button? If the upgrade break something for only a minority of people, they won’t fix it. This could occur anytime without any notification. They just have to add a document.write in their js you call asynchronously and BAM! Your website is just an empty blank page. And as you call many external ressources, it can be very difficult to find the origin of the problem.

+

Using social network buttons:

+
    +
  • Pros: +
      +
    • help user share your website,
    • +
    • can provide a popularity indicator to your users.
    • +
  • +
  • Cons: +
      +
    • you help tracking your users,
    • +
    • generally doesn’t follow the design of your website,
    • +
    • use more computer ressources,
    • +
    • slow down your website,
    • +
    • executing third party js can break things silently.
    • +
  • +
+

Solutions

+

I will provide you two solutions with the following properties:

+
    +
  • Pros: +
      +
    • help user share your website,
    • +
    • doesn’t follow your user,
    • +
    • use almost no computer ressource,
    • +
    • doesn’t slow down your website,
    • +
    • doesn’t execute any third party js on your website.
    • +
  • +
  • Cons: +
      +
    • doesn’t provide any popularity information.
    • +
  • +
+

Solution 1 (no js):

+ +

But you have to replace $url$ by the current url.

+

Solution 2 (Just copy/paste):

+

If you don’t want to write the url yourself, you could use some minimal js:

+ +

Here is the result:

+
+ + +
+

Good looking solutions

+

If you don’t want just text but nice icons. You have many choices:

+
    +
  • Use images <img src="..."/> in the links.
  • +
  • Use icon fonts
  • +
+

As the first solution is pretty straightforward, I’ll explain the second one.

+
    +
  1. Download the icon font here
  2. +
  3. put the font file(s) at some place (here ‘fonts/social_font.ttf’ relatively to your css file)
  4. +
  5. Add this to your css
  6. +
+ +

Now add this to your html:

+

Solution 1 (without js):

+ +

Solution 2 (same with a bit more js):

+ +

Here is the result:

+
+
+

· ·

+
+ +
+

Conclusion

+
    +
  1. You get your design back,
  2. +
  3. You stop to help tracking people,
  4. +
  5. You use less computer ressources and more generally power ressources which is good for the planet,
  6. +
  7. Your web pages will load faster.
  8. +
+

ps: On my personal website I continue to use Google analytics. Therefore, Google (and only Google, not facebook nor twitter) can track you here. But I might change this in the future.

]]>
+
+ + 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.

+
+]]>
+ + + diff --git a/src/Scratch/fr/blog/index.html b/src/Scratch/fr/blog/index.html new file mode 100644 index 0000000..1d6318c --- /dev/null +++ b/src/Scratch/fr/blog/index.html @@ -0,0 +1,364 @@ + + + + + YBlog - Blog + + + + + + + + + + + + + + + +
+ + +
+

Blog

+
+
+
+
+ +

Archive

+ + + +
+ +
+
+ +
+ Yann Esposito© +
+
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/blog/programming-language-experience/index.html b/src/Scratch/fr/blog/programming-language-experience/index.html new file mode 100644 index 0000000..58536a2 --- /dev/null +++ b/src/Scratch/fr/blog/programming-language-experience/index.html @@ -0,0 +1,268 @@ + + + + + YBlog - Expérience avec les languages de programmations + + + + + + + + + + + + + + + + +
+ + +
+

Expérience avec les languages de programmations

+ +
+
+
+
+
+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 :-).

+ +

Je m’en souviens aussi pour avoir copier des codes de jeux vidéo à partir de magasines. La plupart des lignes ressemblaient à

+ +

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’.

+ +

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 :

+ +

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 :

+ +

Il va position le point AA entre A et B. Plus précisément, au barycentre (2A + B)/3.

+ +

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

+
+
+ + + +
+
+ Published on 2011-09-28 +
+ + + +
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/latest/index.html b/src/Scratch/fr/latest/index.html new file mode 100644 index 0000000..46d6f63 --- /dev/null +++ b/src/Scratch/fr/latest/index.html @@ -0,0 +1,90 @@ + + + + + YBlog - latest + + + + + + + + + + + + + + + +
+ + +
+

latest

+
+
+
+
+ +
+ +
+
+ +
+ Yann Esposito© +
+
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/rss/index.html b/src/Scratch/fr/rss/index.html new file mode 100644 index 0000000..f4ce7a1 --- /dev/null +++ b/src/Scratch/fr/rss/index.html @@ -0,0 +1,104 @@ + + + + + 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 + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/softwares/index.html b/src/Scratch/fr/softwares/index.html new file mode 100644 index 0000000..03733e2 --- /dev/null +++ b/src/Scratch/fr/softwares/index.html @@ -0,0 +1,94 @@ + + + + + YBlog - Softwares + + + + + + + + + + + + + + + +
+ + +
+

Softwares

+
+
+
+ + +
+
+ +
+ Yann Esposito© +
+
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/softwares/yaquabubbles/index.html b/src/Scratch/fr/softwares/yaquabubbles/index.html new file mode 100644 index 0000000..ca493e6 --- /dev/null +++ b/src/Scratch/fr/softwares/yaquabubbles/index.html @@ -0,0 +1,92 @@ + + + + + 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 + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/softwares/yclock/index.html b/src/Scratch/fr/softwares/yclock/index.html new file mode 100644 index 0000000..a48773d --- /dev/null +++ b/src/Scratch/fr/softwares/yclock/index.html @@ -0,0 +1,92 @@ + + + + + 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 + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/softwares/ypassword/index.html b/src/Scratch/fr/softwares/ypassword/index.html new file mode 100644 index 0000000..0a4d86e --- /dev/null +++ b/src/Scratch/fr/softwares/ypassword/index.html @@ -0,0 +1,102 @@ + + + + + YBlog - YPassword + + + + + + + + + + + + + + + +
+ + +
+

YPassword

+
+
+
+
+

Une gestion simple, sécurisée et portable de ses mots de passes web.

+

Ici réalisée en elm:

+ +

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 + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/softwares/ypassword/iphoneweb/index.html b/src/Scratch/fr/softwares/ypassword/iphoneweb/index.html new file mode 100644 index 0000000..18683e6 --- /dev/null +++ b/src/Scratch/fr/softwares/ypassword/iphoneweb/index.html @@ -0,0 +1,96 @@ + + + + + YBlog - YPassword + + + + + + + + + + + + + + + +
+ + +
+

YPassword

+
+
+
+
+
+ +
+
+ +
+
+ +
+ Yann Esposito© +
+
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/softwares/ypassword/web/index.html b/src/Scratch/fr/softwares/ypassword/web/index.html new file mode 100644 index 0000000..83672d4 --- /dev/null +++ b/src/Scratch/fr/softwares/ypassword/web/index.html @@ -0,0 +1,96 @@ + + + + + YBlog - YPassword + + + + + + + + + + + + + + + +
+ + +
+

YPassword

+
+
+
+
+
+ +
+
+ +
+
+ +
+ Yann Esposito© +
+
+ Done with + Vim + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/fr/validation/index.html b/src/Scratch/fr/validation/index.html new file mode 100644 index 0000000..6e223f9 --- /dev/null +++ b/src/Scratch/fr/validation/index.html @@ -0,0 +1,95 @@ + + + + + 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 + spacemacs + & + nanoc + Hakyll +
+
+
+ + + ADA: + + + DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp + +
+
+
+
+ +
+ + diff --git a/src/Scratch/img/GitHub-Mark-32px.png b/src/Scratch/img/GitHub-Mark-32px.png new file mode 100644 index 0000000..912236b Binary files /dev/null and b/src/Scratch/img/GitHub-Mark-32px.png differ diff --git a/src/Scratch/img/about/FlatAvatar.png b/src/Scratch/img/about/FlatAvatar.png new file mode 100644 index 0000000..081fc75 Binary files /dev/null and b/src/Scratch/img/about/FlatAvatar.png differ diff --git a/src/Scratch/img/about/FlatAvatar@2x.png b/src/Scratch/img/about/FlatAvatar@2x.png new file mode 100644 index 0000000..5fad33e Binary files /dev/null and b/src/Scratch/img/about/FlatAvatar@2x.png differ diff --git a/src/Scratch/img/about/avatar.png b/src/Scratch/img/about/avatar.png new file mode 100644 index 0000000..9dfa907 Binary files /dev/null and b/src/Scratch/img/about/avatar.png differ diff --git a/src/Scratch/img/about/cv/cv.png b/src/Scratch/img/about/cv/cv.png new file mode 100644 index 0000000..1234014 Binary files /dev/null and b/src/Scratch/img/about/cv/cv.png differ diff --git a/src/Scratch/img/about/yann1.jpg b/src/Scratch/img/about/yann1.jpg new file mode 100644 index 0000000..f99a647 Binary files /dev/null and b/src/Scratch/img/about/yann1.jpg differ diff --git a/src/Scratch/img/ada-logo.png b/src/Scratch/img/ada-logo.png new file mode 100644 index 0000000..125d407 Binary files /dev/null and b/src/Scratch/img/ada-logo.png differ diff --git a/src/Scratch/img/blog/03_losthighway/intro.jpg b/src/Scratch/img/blog/03_losthighway/intro.jpg new file mode 100644 index 0000000..93ca5e4 Binary files /dev/null and b/src/Scratch/img/blog/03_losthighway/intro.jpg differ diff --git a/src/Scratch/img/blog/03_losthighway/mysteryman.jpg b/src/Scratch/img/blog/03_losthighway/mysteryman.jpg new file mode 100644 index 0000000..391a08c Binary files /dev/null and b/src/Scratch/img/blog/03_losthighway/mysteryman.jpg differ diff --git a/src/Scratch/img/blog/03_losthighway/rorschach.gif b/src/Scratch/img/blog/03_losthighway/rorschach.gif new file mode 100644 index 0000000..331ad49 Binary files /dev/null and b/src/Scratch/img/blog/03_losthighway/rorschach.gif differ diff --git a/src/Scratch/img/blog/06_How_I_use_git/central_architecture.png b/src/Scratch/img/blog/06_How_I_use_git/central_architecture.png new file mode 100644 index 0000000..d70f69d Binary files /dev/null and b/src/Scratch/img/blog/06_How_I_use_git/central_architecture.png differ diff --git a/src/Scratch/img/blog/07_Screensaver_compilation_option_for_Snow_Leopard/xcodeConfig.png b/src/Scratch/img/blog/07_Screensaver_compilation_option_for_Snow_Leopard/xcodeConfig.png new file mode 100644 index 0000000..9362576 Binary files /dev/null and b/src/Scratch/img/blog/07_Screensaver_compilation_option_for_Snow_Leopard/xcodeConfig.png differ diff --git a/src/Scratch/img/blog/2010-03-23-Encapsulate-git/branch_hierarchy.png b/src/Scratch/img/blog/2010-03-23-Encapsulate-git/branch_hierarchy.png new file mode 100644 index 0000000..be52068 Binary files /dev/null and b/src/Scratch/img/blog/2010-03-23-Encapsulate-git/branch_hierarchy.png differ diff --git a/src/Scratch/img/blog/2010-03-23-Encapsulate-git/dynamic_branching.png b/src/Scratch/img/blog/2010-03-23-Encapsulate-git/dynamic_branching.png new file mode 100644 index 0000000..bda1c91 Binary files /dev/null and b/src/Scratch/img/blog/2010-03-23-Encapsulate-git/dynamic_branching.png differ diff --git a/src/Scratch/img/blog/2010-06-17-track-events-with-google-analytics/GA_events.png b/src/Scratch/img/blog/2010-06-17-track-events-with-google-analytics/GA_events.png new file mode 100644 index 0000000..75f0283 Binary files /dev/null and b/src/Scratch/img/blog/2010-06-17-track-events-with-google-analytics/GA_events.png differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/Data b/src/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/Data new file mode 100644 index 0000000..bfe3165 Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/Data differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/QuickLook/Preview.jpg b/src/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/QuickLook/Preview.jpg new file mode 100644 index 0000000..260dc71 Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/QuickLook/Preview.jpg differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/QuickLook/Thumbnail.jpg b/src/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/QuickLook/Thumbnail.jpg new file mode 100644 index 0000000..260dc71 Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.drawit/QuickLook/Thumbnail.jpg differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.png b/src/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.png new file mode 100644 index 0000000..eadfb7e Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/3_corps.png differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/Data b/src/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/Data new file mode 100644 index 0000000..6f86add Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/Data differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/QuickLook/Preview.jpg b/src/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/QuickLook/Preview.jpg new file mode 100644 index 0000000..a4374fa Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/QuickLook/Preview.jpg differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/QuickLook/Thumbnail.jpg b/src/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/QuickLook/Thumbnail.jpg new file mode 100644 index 0000000..a4374fa Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.drawit/QuickLook/Thumbnail.jpg differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.png b/src/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.png new file mode 100644 index 0000000..c5d2796 Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/controled_error.png differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/Data b/src/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/Data new file mode 100644 index 0000000..ce6cd9e Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/Data differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/QuickLook/Preview.jpg b/src/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/QuickLook/Preview.jpg new file mode 100644 index 0000000..99b45fa Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/QuickLook/Preview.jpg differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/QuickLook/Thumbnail.jpg b/src/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/QuickLook/Thumbnail.jpg new file mode 100644 index 0000000..aa6e72d Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.drawit/QuickLook/Thumbnail.jpg differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.png b/src/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.png new file mode 100644 index 0000000..3750af4 Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/genesis.png differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/Data b/src/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/Data new file mode 100644 index 0000000..ef5765d Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/Data differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/QuickLook/Preview.jpg b/src/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/QuickLook/Preview.jpg new file mode 100644 index 0000000..32bf75a Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/QuickLook/Preview.jpg differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/QuickLook/Thumbnail.jpg b/src/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/QuickLook/Thumbnail.jpg new file mode 100644 index 0000000..c81dfdc Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.drawit/QuickLook/Thumbnail.jpg differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.png b/src/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.png new file mode 100644 index 0000000..4ace8dd Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/mandelbrot.png differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/Data b/src/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/Data new file mode 100644 index 0000000..1acf118 Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/Data differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/QuickLook/Preview.jpg b/src/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/QuickLook/Preview.jpg new file mode 100644 index 0000000..ab1eb06 Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/QuickLook/Preview.jpg differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/QuickLook/Thumbnail.jpg b/src/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/QuickLook/Thumbnail.jpg new file mode 100644 index 0000000..c88b3da Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.drawit/QuickLook/Thumbnail.jpg differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.png b/src/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.png new file mode 100644 index 0000000..f05fb32 Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/stackOverflow.png differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_3_angles.png b/src/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_3_angles.png new file mode 100644 index 0000000..f3b2010 Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_3_angles.png differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/Data b/src/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/src/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/Data differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/QuickLook/Preview.jpg b/src/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/QuickLook/Preview.jpg new file mode 100644 index 0000000..3465277 Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/QuickLook/Preview.jpg differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/QuickLook/Thumbnail.jpg b/src/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/QuickLook/Thumbnail.jpg new file mode 100644 index 0000000..3465277 Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.drawit/QuickLook/Thumbnail.jpg differ diff --git a/src/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.png b/src/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.png new file mode 100644 index 0000000..ac0b081 Binary files /dev/null and b/src/Scratch/img/blog/2010-07-09-Indecidabilities/triangle_on_sphere.png differ diff --git a/src/Scratch/img/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/main.png b/src/Scratch/img/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/main.png new file mode 100644 index 0000000..e0b36cb Binary files /dev/null and b/src/Scratch/img/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/main.png differ diff --git a/src/Scratch/img/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/sign_icon.png b/src/Scratch/img/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/sign_icon.png new file mode 100644 index 0000000..a9c7388 Binary files /dev/null and b/src/Scratch/img/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/sign_icon.png differ diff --git a/src/Scratch/img/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/main.png b/src/Scratch/img/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/main.png new file mode 100644 index 0000000..37f8955 Binary files /dev/null and b/src/Scratch/img/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/main.png differ diff --git a/src/Scratch/img/blog/2011-04-20-Now-hosted-on-github/main.png b/src/Scratch/img/blog/2011-04-20-Now-hosted-on-github/main.png new file mode 100644 index 0000000..ae9c67d Binary files /dev/null and b/src/Scratch/img/blog/2011-04-20-Now-hosted-on-github/main.png differ diff --git a/src/Scratch/img/blog/Hakyll-setup/dr_jerry.jpg b/src/Scratch/img/blog/Hakyll-setup/dr_jerry.jpg new file mode 100644 index 0000000..e9ec5e9 Binary files /dev/null and b/src/Scratch/img/blog/Hakyll-setup/dr_jerry.jpg differ diff --git a/src/Scratch/img/blog/Hakyll-setup/main.png b/src/Scratch/img/blog/Hakyll-setup/main.png new file mode 100644 index 0000000..ddee95b Binary files /dev/null and b/src/Scratch/img/blog/Hakyll-setup/main.png differ diff --git a/src/Scratch/img/blog/Hakyll-setup/mr_love.jpeg b/src/Scratch/img/blog/Hakyll-setup/mr_love.jpeg new file mode 100644 index 0000000..fe32f80 Binary files /dev/null and b/src/Scratch/img/blog/Hakyll-setup/mr_love.jpeg differ diff --git a/src/Scratch/img/blog/Hakyll-setup/overview.png b/src/Scratch/img/blog/Hakyll-setup/overview.png new file mode 100644 index 0000000..766415f Binary files /dev/null and b/src/Scratch/img/blog/Hakyll-setup/overview.png differ diff --git a/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3DMandelbulbDetail.png b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3DMandelbulbDetail.png new file mode 100644 index 0000000..a76d334 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3DMandelbulbDetail.png differ diff --git a/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3DMandelbulbDetail2.png b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3DMandelbulbDetail2.png new file mode 100644 index 0000000..c86ff9d Binary files /dev/null and b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3DMandelbulbDetail2.png differ diff --git a/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3D_mandel.png b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3D_mandel.png new file mode 100644 index 0000000..bfc22cb Binary files /dev/null and b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3D_mandel.png differ diff --git a/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/BenoitBMandelbrot.jpg b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/BenoitBMandelbrot.jpg new file mode 100644 index 0000000..4f4619f Binary files /dev/null and b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/BenoitBMandelbrot.jpg differ diff --git a/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/GoldenMandelbulb.png b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/GoldenMandelbulb.png new file mode 100644 index 0000000..38c1d84 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/GoldenMandelbulb.png differ diff --git a/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGLMandelEdge.png b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGLMandelEdge.png new file mode 100644 index 0000000..8f6e30b Binary files /dev/null and b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGLMandelEdge.png differ diff --git a/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGLMandelEdges.png b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGLMandelEdges.png new file mode 100644 index 0000000..d48d7ff Binary files /dev/null and b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGLMandelEdges.png differ diff --git a/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGL_Plan.png b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGL_Plan.png new file mode 100644 index 0000000..e00119d Binary files /dev/null and b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGL_Plan.png differ diff --git a/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/hglmandel_v01.png b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/hglmandel_v01.png new file mode 100644 index 0000000..6299c88 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/hglmandel_v01.png differ diff --git a/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/hglmandel_v01_too_wide.png b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/hglmandel_v01_too_wide.png new file mode 100644 index 0000000..bcfeb76 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/hglmandel_v01_too_wide.png differ diff --git a/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/main.png b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/main.png new file mode 100644 index 0000000..8b9acc0 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/main.png differ diff --git a/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/mandelbrot_3D.png b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/mandelbrot_3D.png new file mode 100644 index 0000000..0817259 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/mandelbrot_3D.png differ diff --git a/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/triangles.png b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/triangles.png new file mode 100644 index 0000000..209eb4b Binary files /dev/null and b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/triangles.png differ diff --git a/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/triangles.svg b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/triangles.svg new file mode 100644 index 0000000..a82d5c3 --- /dev/null +++ b/src/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/triangles.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + p1 + p2 + p3 + p4 + + diff --git a/src/Scratch/img/blog/Haskell-Tutorials--a-tutorial/design_is_important.jpg b/src/Scratch/img/blog/Haskell-Tutorials--a-tutorial/design_is_important.jpg new file mode 100644 index 0000000..5175634 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-Tutorials--a-tutorial/design_is_important.jpg differ diff --git a/src/Scratch/img/blog/Haskell-Tutorials--a-tutorial/design_is_important.png b/src/Scratch/img/blog/Haskell-Tutorials--a-tutorial/design_is_important.png new file mode 100644 index 0000000..00ae50f Binary files /dev/null and b/src/Scratch/img/blog/Haskell-Tutorials--a-tutorial/design_is_important.png differ diff --git a/src/Scratch/img/blog/Haskell-Tutorials--a-tutorial/main.jpg b/src/Scratch/img/blog/Haskell-Tutorials--a-tutorial/main.jpg new file mode 100644 index 0000000..39973fb Binary files /dev/null and b/src/Scratch/img/blog/Haskell-Tutorials--a-tutorial/main.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/Haskell-logo.png b/src/Scratch/img/blog/Haskell-the-Hard-Way/Haskell-logo.png new file mode 100644 index 0000000..d36b129 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/Haskell-logo.png differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/dali_reve.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/dali_reve.jpg new file mode 100644 index 0000000..99e53b3 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/dali_reve.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/dangerous_book.png b/src/Scratch/img/blog/Haskell-the-Hard-Way/dangerous_book.png new file mode 100644 index 0000000..31abd49 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/dangerous_book.png differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/escher_infinite_lizards.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/escher_infinite_lizards.jpg new file mode 100644 index 0000000..5d91af6 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/escher_infinite_lizards.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/escher_polygon.png b/src/Scratch/img/blog/Haskell-the-Hard-Way/escher_polygon.png new file mode 100644 index 0000000..9afa3e2 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/escher_polygon.png differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/golconde.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/golconde.jpg new file mode 100644 index 0000000..e1941fb Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/golconde.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/hr_giger_biomechanicallandscape_500.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/hr_giger_biomechanicallandscape_500.jpg new file mode 100644 index 0000000..c3103ea Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/hr_giger_biomechanicallandscape_500.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/jocker_pencil_trick.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/jocker_pencil_trick.jpg new file mode 100644 index 0000000..40d01a1 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/jocker_pencil_trick.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/kandinsky_gugg.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/kandinsky_gugg.jpg new file mode 100644 index 0000000..7091a36 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/kandinsky_gugg.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/learn_haskell_mordor.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/learn_haskell_mordor.jpg new file mode 100644 index 0000000..aff9f6c Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/learn_haskell_mordor.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/magritte-l-arbre.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/magritte-l-arbre.jpg new file mode 100644 index 0000000..f989036 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/magritte-l-arbre.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/magritte_carte_blanche.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/magritte_carte_blanche.jpg new file mode 100644 index 0000000..b846615 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/magritte_carte_blanche.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/magritte_pipe.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/magritte_pipe.jpg new file mode 100644 index 0000000..56c0d7b Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/magritte_pipe.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/magritte_pleasure_principle.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/magritte_pleasure_principle.jpg new file mode 100644 index 0000000..19b03f9 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/magritte_pleasure_principle.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/munch_TheScream.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/munch_TheScream.jpg new file mode 100644 index 0000000..73d1f6a Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/munch_TheScream.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/picasso_owl.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/picasso_owl.jpg new file mode 100644 index 0000000..b4dfcb2 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/picasso_owl.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/salvador-dali-the-madonna-of-port-lligat.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/salvador-dali-the-madonna-of-port-lligat.jpg new file mode 100644 index 0000000..7e47cad Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/salvador-dali-the-madonna-of-port-lligat.jpg differ diff --git a/src/Scratch/img/blog/Haskell-the-Hard-Way/yo_dawg_tree.jpg b/src/Scratch/img/blog/Haskell-the-Hard-Way/yo_dawg_tree.jpg new file mode 100644 index 0000000..7c041c9 Binary files /dev/null and b/src/Scratch/img/blog/Haskell-the-Hard-Way/yo_dawg_tree.jpg differ diff --git a/src/Scratch/img/blog/Helping-avoid-Haskell-Success/main.jpg b/src/Scratch/img/blog/Helping-avoid-Haskell-Success/main.jpg new file mode 100644 index 0000000..a4ba47b Binary files /dev/null and b/src/Scratch/img/blog/Helping-avoid-Haskell-Success/main.jpg differ diff --git a/src/Scratch/img/blog/Higher-order-function-in-zsh/main.jpg b/src/Scratch/img/blog/Higher-order-function-in-zsh/main.jpg new file mode 100644 index 0000000..e402c3c Binary files /dev/null and b/src/Scratch/img/blog/Higher-order-function-in-zsh/main.jpg differ diff --git a/src/Scratch/img/blog/Higher-order-function-in-zsh/src/main.jpg b/src/Scratch/img/blog/Higher-order-function-in-zsh/src/main.jpg new file mode 100644 index 0000000..9ba889c Binary files /dev/null and b/src/Scratch/img/blog/Higher-order-function-in-zsh/src/main.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/a-blessing.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/a-blessing.jpg new file mode 100644 index 0000000..60a0fea Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/a-blessing.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/black-knight.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/black-knight.jpg new file mode 100644 index 0000000..f059a9c Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/black-knight.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/bridge-of-death.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/bridge-of-death.jpg new file mode 100644 index 0000000..1a30bed Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/bridge-of-death.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/bring-out-your-dead.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/bring-out-your-dead.jpg new file mode 100644 index 0000000..b22a686 Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/bring-out-your-dead.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/castle-of-hhhhaaaarr.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/castle-of-hhhhaaaarr.jpg new file mode 100644 index 0000000..960ff43 Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/castle-of-hhhhaaaarr.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/coconut.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/coconut.jpg new file mode 100644 index 0000000..bb480e9 Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/coconut.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/french-insult.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/french-insult.jpg new file mode 100644 index 0000000..6480364 Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/french-insult.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/giant-three-head.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/giant-three-head.jpg new file mode 100644 index 0000000..474256a Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/giant-three-head.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/holy-grail-monty-python.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/holy-grail-monty-python.jpg new file mode 100644 index 0000000..dea8854 Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/holy-grail-monty-python.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/holy-grail.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/holy-grail.jpg new file mode 100644 index 0000000..69aab05 Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/holy-grail.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/holy-grenade.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/holy-grenade.jpg new file mode 100644 index 0000000..1f9ce1b Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/holy-grenade.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/lancelot-run.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/lancelot-run.jpg new file mode 100644 index 0000000..04e61f2 Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/lancelot-run.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/priest.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/priest.jpg new file mode 100644 index 0000000..30810eb Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/priest.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/rabbit.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/rabbit.jpg new file mode 100644 index 0000000..572874f Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/rabbit.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/tim.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/tim.jpg new file mode 100644 index 0000000..4c8282a Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/tim.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/witch.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/witch.jpg new file mode 100644 index 0000000..fae8b76 Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/witch.jpg differ diff --git a/src/Scratch/img/blog/Holy-Haskell-Starter/zoot.jpg b/src/Scratch/img/blog/Holy-Haskell-Starter/zoot.jpg new file mode 100644 index 0000000..42e84d3 Binary files /dev/null and b/src/Scratch/img/blog/Holy-Haskell-Starter/zoot.jpg differ diff --git a/src/Scratch/img/blog/Learn-Vim-Progressively/append-to-many-lines.gif b/src/Scratch/img/blog/Learn-Vim-Progressively/append-to-many-lines.gif new file mode 100644 index 0000000..a1f5c31 Binary files /dev/null and b/src/Scratch/img/blog/Learn-Vim-Progressively/append-to-many-lines.gif differ diff --git a/src/Scratch/img/blog/Learn-Vim-Progressively/autoindent.gif b/src/Scratch/img/blog/Learn-Vim-Progressively/autoindent.gif new file mode 100644 index 0000000..a8f9661 Binary files /dev/null and b/src/Scratch/img/blog/Learn-Vim-Progressively/autoindent.gif differ diff --git a/src/Scratch/img/blog/Learn-Vim-Progressively/completion.gif b/src/Scratch/img/blog/Learn-Vim-Progressively/completion.gif new file mode 100644 index 0000000..f5c5463 Binary files /dev/null and b/src/Scratch/img/blog/Learn-Vim-Progressively/completion.gif differ diff --git a/src/Scratch/img/blog/Learn-Vim-Progressively/line_moves.jpg b/src/Scratch/img/blog/Learn-Vim-Progressively/line_moves.jpg new file mode 100644 index 0000000..418b821 Binary files /dev/null and b/src/Scratch/img/blog/Learn-Vim-Progressively/line_moves.jpg differ diff --git a/src/Scratch/img/blog/Learn-Vim-Progressively/line_moves.txt b/src/Scratch/img/blog/Learn-Vim-Progressively/line_moves.txt new file mode 100644 index 0000000..c598330 --- /dev/null +++ b/src/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/src/Scratch/img/blog/Learn-Vim-Progressively/macros.gif b/src/Scratch/img/blog/Learn-Vim-Progressively/macros.gif new file mode 100644 index 0000000..a6aec08 Binary files /dev/null and b/src/Scratch/img/blog/Learn-Vim-Progressively/macros.gif differ diff --git a/src/Scratch/img/blog/Learn-Vim-Progressively/rectangular-blocks.gif b/src/Scratch/img/blog/Learn-Vim-Progressively/rectangular-blocks.gif new file mode 100644 index 0000000..d96224c Binary files /dev/null and b/src/Scratch/img/blog/Learn-Vim-Progressively/rectangular-blocks.gif differ diff --git a/src/Scratch/img/blog/Learn-Vim-Progressively/six-million-dollar-man-a-bionic-christmas-carol-17.jpg b/src/Scratch/img/blog/Learn-Vim-Progressively/six-million-dollar-man-a-bionic-christmas-carol-17.jpg new file mode 100644 index 0000000..7c63208 Binary files /dev/null and b/src/Scratch/img/blog/Learn-Vim-Progressively/six-million-dollar-man-a-bionic-christmas-carol-17.jpg differ diff --git a/src/Scratch/img/blog/Learn-Vim-Progressively/split.gif b/src/Scratch/img/blog/Learn-Vim-Progressively/split.gif new file mode 100644 index 0000000..9013c11 Binary files /dev/null and b/src/Scratch/img/blog/Learn-Vim-Progressively/split.gif differ diff --git a/src/Scratch/img/blog/Learn-Vim-Progressively/textobjects.png b/src/Scratch/img/blog/Learn-Vim-Progressively/textobjects.png new file mode 100644 index 0000000..951d640 Binary files /dev/null and b/src/Scratch/img/blog/Learn-Vim-Progressively/textobjects.png differ diff --git a/src/Scratch/img/blog/Learn-Vim-Progressively/the_six_million_dollar_man_image_04.jpg b/src/Scratch/img/blog/Learn-Vim-Progressively/the_six_million_dollar_man_image_04.jpg new file mode 100644 index 0000000..42b28f4 Binary files /dev/null and b/src/Scratch/img/blog/Learn-Vim-Progressively/the_six_million_dollar_man_image_04.jpg differ diff --git a/src/Scratch/img/blog/Learn-Vim-Progressively/uber_leet_use_vim.jpg b/src/Scratch/img/blog/Learn-Vim-Progressively/uber_leet_use_vim.jpg new file mode 100644 index 0000000..09490f4 Binary files /dev/null and b/src/Scratch/img/blog/Learn-Vim-Progressively/uber_leet_use_vim.jpg differ diff --git a/src/Scratch/img/blog/Learn-Vim-Progressively/word_moves.jpg b/src/Scratch/img/blog/Learn-Vim-Progressively/word_moves.jpg new file mode 100644 index 0000000..22a8c31 Binary files /dev/null and b/src/Scratch/img/blog/Learn-Vim-Progressively/word_moves.jpg differ diff --git a/src/Scratch/img/blog/Password-Management/main.png b/src/Scratch/img/blog/Password-Management/main.png new file mode 100644 index 0000000..9f89dbd Binary files /dev/null and b/src/Scratch/img/blog/Password-Management/main.png differ diff --git a/src/Scratch/img/blog/Rational-Web-Framework-Choice/battle-of-lepanto-vicentino-andrea.jpg b/src/Scratch/img/blog/Rational-Web-Framework-Choice/battle-of-lepanto-vicentino-andrea.jpg new file mode 100644 index 0000000..0d154b9 Binary files /dev/null and b/src/Scratch/img/blog/Rational-Web-Framework-Choice/battle-of-lepanto-vicentino-andrea.jpg differ diff --git a/src/Scratch/img/blog/Rational-Web-Framework-Choice/choice_paralysis.gif b/src/Scratch/img/blog/Rational-Web-Framework-Choice/choice_paralysis.gif new file mode 100644 index 0000000..68fa968 Binary files /dev/null and b/src/Scratch/img/blog/Rational-Web-Framework-Choice/choice_paralysis.gif differ diff --git a/src/Scratch/img/blog/Rational-Web-Framework-Choice/css_is_awesome.jpg b/src/Scratch/img/blog/Rational-Web-Framework-Choice/css_is_awesome.jpg new file mode 100644 index 0000000..21e2ffe Binary files /dev/null and b/src/Scratch/img/blog/Rational-Web-Framework-Choice/css_is_awesome.jpg differ diff --git a/src/Scratch/img/blog/Rational-Web-Framework-Choice/languagesafety.png b/src/Scratch/img/blog/Rational-Web-Framework-Choice/languagesafety.png new file mode 100644 index 0000000..c9e686f Binary files /dev/null and b/src/Scratch/img/blog/Rational-Web-Framework-Choice/languagesafety.png differ diff --git a/src/Scratch/img/blog/Rational-Web-Framework-Choice/mainstreamlanguages.png b/src/Scratch/img/blog/Rational-Web-Framework-Choice/mainstreamlanguages.png new file mode 100644 index 0000000..c6dbfee Binary files /dev/null and b/src/Scratch/img/blog/Rational-Web-Framework-Choice/mainstreamlanguages.png differ diff --git a/src/Scratch/img/blog/Rational-Web-Framework-Choice/secondtierlanguages.png b/src/Scratch/img/blog/Rational-Web-Framework-Choice/secondtierlanguages.png new file mode 100644 index 0000000..229683d Binary files /dev/null and b/src/Scratch/img/blog/Rational-Web-Framework-Choice/secondtierlanguages.png differ diff --git a/src/Scratch/img/blog/Rational-Web-Framework-Choice/typesystem.png b/src/Scratch/img/blog/Rational-Web-Framework-Choice/typesystem.png new file mode 100644 index 0000000..ccd8ff1 Binary files /dev/null and b/src/Scratch/img/blog/Rational-Web-Framework-Choice/typesystem.png differ diff --git a/src/Scratch/img/blog/SVG-and-m4-fractals/compilelogo.sh b/src/Scratch/img/blog/SVG-and-m4-fractals/compilelogo.sh new file mode 100755 index 0000000..2c57cec --- /dev/null +++ b/src/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/src/Scratch/img/blog/SVG-and-m4-fractals/main.m4 b/src/Scratch/img/blog/SVG-and-m4-fractals/main.m4 new file mode 100644 index 0000000..39ab18c --- /dev/null +++ b/src/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/src/Scratch/img/blog/SVG-and-m4-fractals/main.png b/src/Scratch/img/blog/SVG-and-m4-fractals/main.png new file mode 100644 index 0000000..6e3f939 Binary files /dev/null and b/src/Scratch/img/blog/SVG-and-m4-fractals/main.png differ diff --git a/src/Scratch/img/blog/SVG-and-m4-fractals/main.svg b/src/Scratch/img/blog/SVG-and-m4-fractals/main.svg new file mode 100644 index 0000000..1ce6726 --- /dev/null +++ b/src/Scratch/img/blog/SVG-and-m4-fractals/main.svg @@ -0,0 +1,71 @@ + + + + + + λ + + + esod + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Scratch/img/blog/SVG-and-m4-fractals/warp-benchmark.png b/src/Scratch/img/blog/SVG-and-m4-fractals/warp-benchmark.png new file mode 100644 index 0000000..1f27ebd Binary files /dev/null and b/src/Scratch/img/blog/SVG-and-m4-fractals/warp-benchmark.png differ diff --git a/src/Scratch/img/blog/Safer-Haskell-Install/main.jpg b/src/Scratch/img/blog/Safer-Haskell-Install/main.jpg new file mode 100644 index 0000000..40382df Binary files /dev/null and b/src/Scratch/img/blog/Safer-Haskell-Install/main.jpg differ diff --git a/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/Data b/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/Data new file mode 100644 index 0000000..dfc2a1b Binary files /dev/null and b/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/Data differ diff --git a/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/QuickLook/Preview.png b/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/QuickLook/Preview.png new file mode 100644 index 0000000..6b5d497 Binary files /dev/null and b/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/QuickLook/Preview.png differ diff --git a/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/QuickLook/Thumbnail.png b/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/QuickLook/Thumbnail.png new file mode 100644 index 0000000..871169f Binary files /dev/null and b/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/QuickLook/Thumbnail.png differ diff --git a/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/metadata b/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/metadata new file mode 100644 index 0000000..22f1870 --- /dev/null +++ b/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/metadata @@ -0,0 +1,23 @@ + + + + + app + com.bohemiancoding.sketch + build + 5370 + commit + 38aeabf36c76d40a8ed2d256f8f3b9492c3dac07 + fonts + + Futura-MediumItalic + Futura-Medium + Futura-Medium + Futura-Medium + + length + 846015 + version + 18 + + diff --git a/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/version b/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/version new file mode 100644 index 0000000..25bf17f --- /dev/null +++ b/src/Scratch/img/blog/Safer-Haskell-Install/main.sketch/version @@ -0,0 +1 @@ +18 \ No newline at end of file diff --git a/src/Scratch/img/blog/Social-link-the-right-way/main.png b/src/Scratch/img/blog/Social-link-the-right-way/main.png new file mode 100644 index 0000000..82bd48b Binary files /dev/null and b/src/Scratch/img/blog/Social-link-the-right-way/main.png differ diff --git a/src/Scratch/img/blog/Typography-and-the-Web/first_latex_screenshot.png b/src/Scratch/img/blog/Typography-and-the-Web/first_latex_screenshot.png new file mode 100644 index 0000000..b9421fa Binary files /dev/null and b/src/Scratch/img/blog/Typography-and-the-Web/first_latex_screenshot.png differ diff --git a/src/Scratch/img/blog/Typography-and-the-Web/first_sc_screenshot.png b/src/Scratch/img/blog/Typography-and-the-Web/first_sc_screenshot.png new file mode 100644 index 0000000..6915af1 Binary files /dev/null and b/src/Scratch/img/blog/Typography-and-the-Web/first_sc_screenshot.png differ diff --git a/src/Scratch/img/blog/Typography-and-the-Web/ligatures.png b/src/Scratch/img/blog/Typography-and-the-Web/ligatures.png new file mode 100644 index 0000000..15970fd Binary files /dev/null and b/src/Scratch/img/blog/Typography-and-the-Web/ligatures.png differ diff --git a/src/Scratch/img/blog/Typography-and-the-Web/xelatex_ligatures.jpg b/src/Scratch/img/blog/Typography-and-the-Web/xelatex_ligatures.jpg new file mode 100644 index 0000000..b9e6e0d Binary files /dev/null and b/src/Scratch/img/blog/Typography-and-the-Web/xelatex_ligatures.jpg differ diff --git a/src/Scratch/img/blog/Vim-as-IDE/80thcolumn.png b/src/Scratch/img/blog/Vim-as-IDE/80thcolumn.png new file mode 100644 index 0000000..01395e2 Binary files /dev/null and b/src/Scratch/img/blog/Vim-as-IDE/80thcolumn.png differ diff --git a/src/Scratch/img/blog/Vim-as-IDE/Gread.gif b/src/Scratch/img/blog/Vim-as-IDE/Gread.gif new file mode 100644 index 0000000..33a486f Binary files /dev/null and b/src/Scratch/img/blog/Vim-as-IDE/Gread.gif differ diff --git a/src/Scratch/img/blog/Vim-as-IDE/auto-typing.gif b/src/Scratch/img/blog/Vim-as-IDE/auto-typing.gif new file mode 100644 index 0000000..0e6611e Binary files /dev/null and b/src/Scratch/img/blog/Vim-as-IDE/auto-typing.gif differ diff --git a/src/Scratch/img/blog/Vim-as-IDE/clojure.gif b/src/Scratch/img/blog/Vim-as-IDE/clojure.gif new file mode 100644 index 0000000..e9e7037 Binary files /dev/null and b/src/Scratch/img/blog/Vim-as-IDE/clojure.gif differ diff --git a/src/Scratch/img/blog/Vim-as-IDE/easy-align.gif b/src/Scratch/img/blog/Vim-as-IDE/easy-align.gif new file mode 100644 index 0000000..fff4fed Binary files /dev/null and b/src/Scratch/img/blog/Vim-as-IDE/easy-align.gif differ diff --git a/src/Scratch/img/blog/Vim-as-IDE/git-gutter.png b/src/Scratch/img/blog/Vim-as-IDE/git-gutter.png new file mode 100644 index 0000000..a1b121e Binary files /dev/null and b/src/Scratch/img/blog/Vim-as-IDE/git-gutter.png differ diff --git a/src/Scratch/img/blog/Vim-as-IDE/solarized.png b/src/Scratch/img/blog/Vim-as-IDE/solarized.png new file mode 100644 index 0000000..4c7e4cd Binary files /dev/null and b/src/Scratch/img/blog/Vim-as-IDE/solarized.png differ diff --git a/src/Scratch/img/blog/Vim-as-IDE/trim.gif b/src/Scratch/img/blog/Vim-as-IDE/trim.gif new file mode 100644 index 0000000..9860235 Binary files /dev/null and b/src/Scratch/img/blog/Vim-as-IDE/trim.gif differ diff --git a/src/Scratch/img/blog/Vim-as-IDE/unite.gif b/src/Scratch/img/blog/Vim-as-IDE/unite.gif new file mode 100644 index 0000000..a29f206 Binary files /dev/null and b/src/Scratch/img/blog/Vim-as-IDE/unite.gif differ diff --git a/src/Scratch/img/blog/Vim-as-IDE/vim-lint.gif b/src/Scratch/img/blog/Vim-as-IDE/vim-lint.gif new file mode 100644 index 0000000..7e88794 Binary files /dev/null and b/src/Scratch/img/blog/Vim-as-IDE/vim-lint.gif differ diff --git a/src/Scratch/img/blog/Vim-as-IDE/vim_spock.jpg b/src/Scratch/img/blog/Vim-as-IDE/vim_spock.jpg new file mode 100644 index 0000000..f329bd8 Binary files /dev/null and b/src/Scratch/img/blog/Vim-as-IDE/vim_spock.jpg differ diff --git a/src/Scratch/img/blog/Yesod-excellent-ideas/compilelogo.sh b/src/Scratch/img/blog/Yesod-excellent-ideas/compilelogo.sh new file mode 100755 index 0000000..3396531 --- /dev/null +++ b/src/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/src/Scratch/img/blog/Yesod-excellent-ideas/dessin.m4 b/src/Scratch/img/blog/Yesod-excellent-ideas/dessin.m4 new file mode 100644 index 0000000..ade366c --- /dev/null +++ b/src/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/src/Scratch/img/blog/Yesod-excellent-ideas/dessin.svg b/src/Scratch/img/blog/Yesod-excellent-ideas/dessin.svg new file mode 100644 index 0000000..a3d5422 --- /dev/null +++ b/src/Scratch/img/blog/Yesod-excellent-ideas/dessin.svg @@ -0,0 +1,76 @@ + + + + + + + λ + + esod + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Scratch/img/blog/Yesod-excellent-ideas/main.png b/src/Scratch/img/blog/Yesod-excellent-ideas/main.png new file mode 100644 index 0000000..6e3f939 Binary files /dev/null and b/src/Scratch/img/blog/Yesod-excellent-ideas/main.png differ diff --git a/src/Scratch/img/blog/Yesod-excellent-ideas/main.svg b/src/Scratch/img/blog/Yesod-excellent-ideas/main.svg new file mode 100644 index 0000000..d9ea957 --- /dev/null +++ b/src/Scratch/img/blog/Yesod-excellent-ideas/main.svg @@ -0,0 +1,397 @@ + + + + + + + + + + image/svg+xml + + + + + + + + λ + λ + λ + + λ + λ + λ + λ + λ + λ + λ + λ + λ + λ + λ + λ + λ + λ + esod + λ + + λ + λ + λ + + λ + λ + + diff --git a/src/Scratch/img/blog/Yesod-excellent-ideas/yesod-logo.svg b/src/Scratch/img/blog/Yesod-excellent-ideas/yesod-logo.svg new file mode 100644 index 0000000..ff84487 --- /dev/null +++ b/src/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/src/Scratch/img/blog/Yesod-tutorial-for-newbies/flying_neo.jpg b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/flying_neo.jpg new file mode 100644 index 0000000..48694ac Binary files /dev/null and b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/flying_neo.jpg differ diff --git a/src/Scratch/img/blog/Yesod-tutorial-for-newbies/haskell-benchmark.png b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/haskell-benchmark.png new file mode 100644 index 0000000..09ea7a4 Binary files /dev/null and b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/haskell-benchmark.png differ diff --git a/src/Scratch/img/blog/Yesod-tutorial-for-newbies/mirror.jpg b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/mirror.jpg new file mode 100644 index 0000000..177ce7c Binary files /dev/null and b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/mirror.jpg differ diff --git a/src/Scratch/img/blog/Yesod-tutorial-for-newbies/neo_bullet_proof.jpg b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/neo_bullet_proof.jpg new file mode 100644 index 0000000..c3dc684 Binary files /dev/null and b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/neo_bullet_proof.jpg differ diff --git a/src/Scratch/img/blog/Yesod-tutorial-for-newbies/owl_draw.png b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/owl_draw.png new file mode 100644 index 0000000..84b516d Binary files /dev/null and b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/owl_draw.png differ diff --git a/src/Scratch/img/blog/Yesod-tutorial-for-newbies/snap-benchmark.png b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/snap-benchmark.png new file mode 100644 index 0000000..90d62c8 Binary files /dev/null and b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/snap-benchmark.png differ diff --git a/src/Scratch/img/blog/Yesod-tutorial-for-newbies/thousands_smiths.jpg b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/thousands_smiths.jpg new file mode 100644 index 0000000..0b89344 Binary files /dev/null and b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/thousands_smiths.jpg differ diff --git a/src/Scratch/img/blog/Yesod-tutorial-for-newbies/warp-benchmark.png b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/warp-benchmark.png new file mode 100644 index 0000000..ec1ee44 Binary files /dev/null and b/src/Scratch/img/blog/Yesod-tutorial-for-newbies/warp-benchmark.png differ diff --git a/src/Scratch/img/blog/mvc/Screenshot_v0.png b/src/Scratch/img/blog/mvc/Screenshot_v0.png new file mode 100644 index 0000000..045c3e9 Binary files /dev/null and b/src/Scratch/img/blog/mvc/Screenshot_v0.png differ diff --git a/src/Scratch/img/blog/programming-language-experience/C.jpg b/src/Scratch/img/blog/programming-language-experience/C.jpg new file mode 100644 index 0000000..af14f37 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/C.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/basic.gif b/src/Scratch/img/blog/programming-language-experience/basic.gif new file mode 100644 index 0000000..63388d8 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/basic.gif differ diff --git a/src/Scratch/img/blog/programming-language-experience/basic.jpg b/src/Scratch/img/blog/programming-language-experience/basic.jpg new file mode 100644 index 0000000..3683d6a Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/basic.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/cplusplus.jpg b/src/Scratch/img/blog/programming-language-experience/cplusplus.jpg new file mode 100644 index 0000000..92ce559 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/cplusplus.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/dragon.jpg b/src/Scratch/img/blog/programming-language-experience/dragon.jpg new file mode 100644 index 0000000..cf9d717 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/dragon.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/eiffel.jpg b/src/Scratch/img/blog/programming-language-experience/eiffel.jpg new file mode 100644 index 0000000..fd626a3 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/eiffel.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/grail.jpg b/src/Scratch/img/blog/programming-language-experience/grail.jpg new file mode 100644 index 0000000..d60cc40 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/grail.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/php.jpg b/src/Scratch/img/blog/programming-language-experience/php.jpg new file mode 100644 index 0000000..e117ff8 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/php.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/python.jpg b/src/Scratch/img/blog/programming-language-experience/python.jpg new file mode 100644 index 0000000..1a8f75a Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/python.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/src/C.jpg b/src/Scratch/img/blog/programming-language-experience/src/C.jpg new file mode 100644 index 0000000..15ff8e0 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/src/C.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/src/basic.gif b/src/Scratch/img/blog/programming-language-experience/src/basic.gif new file mode 100644 index 0000000..21a3385 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/src/basic.gif differ diff --git a/src/Scratch/img/blog/programming-language-experience/src/basic.jpg b/src/Scratch/img/blog/programming-language-experience/src/basic.jpg new file mode 100644 index 0000000..9e7956e Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/src/basic.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/src/cplusplus.jpg b/src/Scratch/img/blog/programming-language-experience/src/cplusplus.jpg new file mode 100644 index 0000000..32f8c7c Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/src/cplusplus.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/src/dragon.jpg b/src/Scratch/img/blog/programming-language-experience/src/dragon.jpg new file mode 100644 index 0000000..120e27d Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/src/dragon.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/src/eiffel.jpg b/src/Scratch/img/blog/programming-language-experience/src/eiffel.jpg new file mode 100644 index 0000000..99d2e59 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/src/eiffel.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/src/grail.jpg b/src/Scratch/img/blog/programming-language-experience/src/grail.jpg new file mode 100644 index 0000000..a192494 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/src/grail.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/src/php.jpg b/src/Scratch/img/blog/programming-language-experience/src/php.jpg new file mode 100644 index 0000000..5f40819 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/src/php.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/src/python.jpg b/src/Scratch/img/blog/programming-language-experience/src/python.jpg new file mode 100644 index 0000000..5944157 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/src/python.jpg differ diff --git a/src/Scratch/img/blog/programming-language-experience/src/xcode_logo.png b/src/Scratch/img/blog/programming-language-experience/src/xcode_logo.png new file mode 100644 index 0000000..bf7df36 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/src/xcode_logo.png differ diff --git a/src/Scratch/img/blog/programming-language-experience/xcode_logo.png b/src/Scratch/img/blog/programming-language-experience/xcode_logo.png new file mode 100644 index 0000000..dadc784 Binary files /dev/null and b/src/Scratch/img/blog/programming-language-experience/xcode_logo.png differ diff --git a/src/Scratch/img/bullet-feed.png b/src/Scratch/img/bullet-feed.png new file mode 100644 index 0000000..f36fe40 Binary files /dev/null and b/src/Scratch/img/bullet-feed.png differ diff --git a/src/Scratch/img/favicon.ico b/src/Scratch/img/favicon.ico new file mode 100644 index 0000000..3372902 Binary files /dev/null and b/src/Scratch/img/favicon.ico differ diff --git a/src/Scratch/img/keybase.svg b/src/Scratch/img/keybase.svg new file mode 100644 index 0000000..7ac7d87 --- /dev/null +++ b/src/Scratch/img/keybase.svg @@ -0,0 +1 @@ +Keybase icon \ No newline at end of file diff --git a/src/Scratch/img/loading.gif b/src/Scratch/img/loading.gif new file mode 100644 index 0000000..17ae831 Binary files /dev/null and b/src/Scratch/img/loading.gif differ diff --git a/src/Scratch/img/menu/rss-128.png b/src/Scratch/img/menu/rss-128.png new file mode 100644 index 0000000..b660fe5 Binary files /dev/null and b/src/Scratch/img/menu/rss-128.png differ diff --git a/src/Scratch/img/menu/rss-32.png b/src/Scratch/img/menu/rss-32.png new file mode 100644 index 0000000..3315167 Binary files /dev/null and b/src/Scratch/img/menu/rss-32.png differ diff --git a/src/Scratch/img/menu/rss-48.png b/src/Scratch/img/menu/rss-48.png new file mode 100644 index 0000000..800f569 Binary files /dev/null and b/src/Scratch/img/menu/rss-48.png differ diff --git a/src/Scratch/img/menu/rss-64.png b/src/Scratch/img/menu/rss-64.png new file mode 100644 index 0000000..dad8619 Binary files /dev/null and b/src/Scratch/img/menu/rss-64.png differ diff --git a/src/Scratch/img/pinboard.png b/src/Scratch/img/pinboard.png new file mode 100644 index 0000000..d33f32f Binary files /dev/null and b/src/Scratch/img/pinboard.png differ diff --git a/src/Scratch/img/presentation.drawit/Data b/src/Scratch/img/presentation.drawit/Data new file mode 100644 index 0000000..117607d Binary files /dev/null and b/src/Scratch/img/presentation.drawit/Data differ diff --git a/src/Scratch/img/presentation.drawit/Info.plist b/src/Scratch/img/presentation.drawit/Info.plist new file mode 100644 index 0000000..6e899a6 --- /dev/null +++ b/src/Scratch/img/presentation.drawit/Info.plist @@ -0,0 +1,8 @@ + + + + + fileVersion + 2 + + diff --git a/src/Scratch/img/presentation.drawit/QuickLook/Preview.jpg b/src/Scratch/img/presentation.drawit/QuickLook/Preview.jpg new file mode 100644 index 0000000..2c21021 Binary files /dev/null and b/src/Scratch/img/presentation.drawit/QuickLook/Preview.jpg differ diff --git a/src/Scratch/img/presentation.drawit/QuickLook/Thumbnail.jpg b/src/Scratch/img/presentation.drawit/QuickLook/Thumbnail.jpg new file mode 100644 index 0000000..9a2f38b Binary files /dev/null and b/src/Scratch/img/presentation.drawit/QuickLook/Thumbnail.jpg differ diff --git a/src/Scratch/img/presentation.png b/src/Scratch/img/presentation.png new file mode 100644 index 0000000..0d0875e Binary files /dev/null and b/src/Scratch/img/presentation.png differ diff --git a/src/Scratch/img/softwares/yaquabubbles/screenshot1.png b/src/Scratch/img/softwares/yaquabubbles/screenshot1.png new file mode 100644 index 0000000..473347e Binary files /dev/null and b/src/Scratch/img/softwares/yaquabubbles/screenshot1.png differ diff --git a/src/Scratch/img/softwares/yclock/screenshot1.png b/src/Scratch/img/softwares/yclock/screenshot1.png new file mode 100644 index 0000000..d1fca04 Binary files /dev/null and b/src/Scratch/img/softwares/yclock/screenshot1.png differ diff --git a/src/Scratch/img/stackoverflow-logo.png b/src/Scratch/img/stackoverflow-logo.png new file mode 100644 index 0000000..392531d Binary files /dev/null and b/src/Scratch/img/stackoverflow-logo.png differ diff --git a/src/YBlog/YPassword_files/YPassword-1.6.zip b/src/YBlog/YPassword_files/YPassword-1.6.zip new file mode 100644 index 0000000..769002d Binary files /dev/null and b/src/YBlog/YPassword_files/YPassword-1.6.zip differ diff --git a/src/YBlog/YPassword_files/forcePaste.app.zip b/src/YBlog/YPassword_files/forcePaste.app.zip new file mode 100644 index 0000000..8cd3cc3 Binary files /dev/null and b/src/YBlog/YPassword_files/forcePaste.app.zip differ diff --git a/src/YPassword/elm.js b/src/YPassword/elm.js new file mode 100644 index 0000000..a47997d --- /dev/null +++ b/src/YPassword/elm.js @@ -0,0 +1,16183 @@ +var Elm = Elm || { Native: {} }; +Elm.Array = Elm.Array || {}; +Elm.Array.make = function (_elm) { + "use strict"; + _elm.Array = _elm.Array || {}; + if (_elm.Array.values) + return _elm.Array.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Array", + $Basics = Elm.Basics.make(_elm), + $List = Elm.List.make(_elm), + $Maybe = Elm.Maybe.make(_elm), + $Native$Array = Elm.Native.Array.make(_elm); + var append = $Native$Array.append; + var length = $Native$Array.length; + var slice = $Native$Array.slice; + var set = $Native$Array.set; + var get = F2(function (i, + array) { + return _U.cmp(0, + i) < 1 && _U.cmp(i, + $Native$Array.length(array)) < 0 ? $Maybe.Just(A2($Native$Array.get, + i, + array)) : $Maybe.Nothing; + }); + var push = $Native$Array.push; + var empty = $Native$Array.empty; + var filter = F2(function (isOkay, + arr) { + return function () { + var update = F2(function (x, + xs) { + return isOkay(x) ? A2($Native$Array.push, + x, + xs) : xs; + }); + return A3($Native$Array.foldl, + update, + $Native$Array.empty, + arr); + }(); + }); + var foldr = $Native$Array.foldr; + var foldl = $Native$Array.foldl; + var indexedMap = $Native$Array.indexedMap; + var map = $Native$Array.map; + var toIndexedList = function (array) { + return A3($List.map2, + F2(function (v0,v1) { + return {ctor: "_Tuple2" + ,_0: v0 + ,_1: v1}; + }), + _L.range(0, + $Native$Array.length(array) - 1), + $Native$Array.toList(array)); + }; + var toList = $Native$Array.toList; + var fromList = $Native$Array.fromList; + var initialize = $Native$Array.initialize; + var repeat = F2(function (n,e) { + return A2(initialize, + n, + $Basics.always(e)); + }); + var Array = {ctor: "Array"}; + _elm.Array.values = {_op: _op + ,empty: empty + ,repeat: repeat + ,initialize: initialize + ,fromList: fromList + ,length: length + ,push: push + ,append: append + ,get: get + ,set: set + ,slice: slice + ,toList: toList + ,toIndexedList: toIndexedList + ,map: map + ,indexedMap: indexedMap + ,filter: filter + ,foldl: foldl + ,foldr: foldr}; + return _elm.Array.values; +}; +Elm.Basics = Elm.Basics || {}; +Elm.Basics.make = function (_elm) { + "use strict"; + _elm.Basics = _elm.Basics || {}; + if (_elm.Basics.values) + return _elm.Basics.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Basics", + $Native$Basics = Elm.Native.Basics.make(_elm), + $Native$Show = Elm.Native.Show.make(_elm), + $Native$Utils = Elm.Native.Utils.make(_elm); + var uncurry = F2(function (f, + _v0) { + return function () { + switch (_v0.ctor) + {case "_Tuple2": return A2(f, + _v0._0, + _v0._1);} + _U.badCase($moduleName, + "on line 595, column 3 to 8"); + }(); + }); + var curry = F3(function (f, + a, + b) { + return f({ctor: "_Tuple2" + ,_0: a + ,_1: b}); + }); + var flip = F3(function (f,b,a) { + return A2(f,a,b); + }); + var snd = function (_v4) { + return function () { + switch (_v4.ctor) + {case "_Tuple2": return _v4._1;} + _U.badCase($moduleName, + "on line 573, column 3 to 4"); + }(); + }; + var fst = function (_v8) { + return function () { + switch (_v8.ctor) + {case "_Tuple2": return _v8._0;} + _U.badCase($moduleName, + "on line 567, column 3 to 4"); + }(); + }; + var always = F2(function (a, + _v12) { + return function () { + return a; + }(); + }); + var identity = function (x) { + return x; + }; + _op["<|"] = F2(function (f,x) { + return f(x); + }); + _op["|>"] = F2(function (x,f) { + return f(x); + }); + _op[">>"] = F3(function (f, + g, + x) { + return g(f(x)); + }); + _op["<<"] = F3(function (g, + f, + x) { + return g(f(x)); + }); + _op["++"] = $Native$Utils.append; + var toString = $Native$Show.toString; + var isInfinite = $Native$Basics.isInfinite; + var isNaN = $Native$Basics.isNaN; + var toFloat = $Native$Basics.toFloat; + var ceiling = $Native$Basics.ceiling; + var floor = $Native$Basics.floor; + var truncate = $Native$Basics.truncate; + var round = $Native$Basics.round; + var otherwise = true; + var not = $Native$Basics.not; + var xor = $Native$Basics.xor; + _op["||"] = $Native$Basics.or; + _op["&&"] = $Native$Basics.and; + var max = $Native$Basics.max; + var min = $Native$Basics.min; + var GT = {ctor: "GT"}; + var EQ = {ctor: "EQ"}; + var LT = {ctor: "LT"}; + var compare = $Native$Basics.compare; + _op[">="] = $Native$Basics.ge; + _op["<="] = $Native$Basics.le; + _op[">"] = $Native$Basics.gt; + _op["<"] = $Native$Basics.lt; + _op["/="] = $Native$Basics.neq; + _op["=="] = $Native$Basics.eq; + var e = $Native$Basics.e; + var pi = $Native$Basics.pi; + var clamp = $Native$Basics.clamp; + var logBase = $Native$Basics.logBase; + var abs = $Native$Basics.abs; + var negate = $Native$Basics.negate; + var sqrt = $Native$Basics.sqrt; + var atan2 = $Native$Basics.atan2; + var atan = $Native$Basics.atan; + var asin = $Native$Basics.asin; + var acos = $Native$Basics.acos; + var tan = $Native$Basics.tan; + var sin = $Native$Basics.sin; + var cos = $Native$Basics.cos; + _op["^"] = $Native$Basics.exp; + _op["%"] = $Native$Basics.mod; + var rem = $Native$Basics.rem; + _op["//"] = $Native$Basics.div; + _op["/"] = $Native$Basics.floatDiv; + _op["*"] = $Native$Basics.mul; + _op["-"] = $Native$Basics.sub; + _op["+"] = $Native$Basics.add; + var toPolar = $Native$Basics.toPolar; + var fromPolar = $Native$Basics.fromPolar; + var turns = $Native$Basics.turns; + var degrees = $Native$Basics.degrees; + var radians = function (t) { + return t; + }; + _elm.Basics.values = {_op: _op + ,max: max + ,min: min + ,compare: compare + ,not: not + ,xor: xor + ,otherwise: otherwise + ,rem: rem + ,negate: negate + ,abs: abs + ,sqrt: sqrt + ,clamp: clamp + ,logBase: logBase + ,e: e + ,pi: pi + ,cos: cos + ,sin: sin + ,tan: tan + ,acos: acos + ,asin: asin + ,atan: atan + ,atan2: atan2 + ,round: round + ,floor: floor + ,ceiling: ceiling + ,truncate: truncate + ,toFloat: toFloat + ,degrees: degrees + ,radians: radians + ,turns: turns + ,toPolar: toPolar + ,fromPolar: fromPolar + ,isNaN: isNaN + ,isInfinite: isInfinite + ,toString: toString + ,fst: fst + ,snd: snd + ,identity: identity + ,always: always + ,flip: flip + ,curry: curry + ,uncurry: uncurry + ,LT: LT + ,EQ: EQ + ,GT: GT}; + return _elm.Basics.values; +}; +Elm.Char = Elm.Char || {}; +Elm.Char.make = function (_elm) { + "use strict"; + _elm.Char = _elm.Char || {}; + if (_elm.Char.values) + return _elm.Char.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Char", + $Basics = Elm.Basics.make(_elm), + $Native$Char = Elm.Native.Char.make(_elm); + var fromCode = $Native$Char.fromCode; + var toCode = $Native$Char.toCode; + var toLocaleLower = $Native$Char.toLocaleLower; + var toLocaleUpper = $Native$Char.toLocaleUpper; + var toLower = $Native$Char.toLower; + var toUpper = $Native$Char.toUpper; + var isBetween = F3(function (low, + high, + $char) { + return function () { + var code = toCode($char); + return _U.cmp(code, + toCode(low)) > -1 && _U.cmp(code, + toCode(high)) < 1; + }(); + }); + var isUpper = A2(isBetween, + _U.chr("A"), + _U.chr("Z")); + var isLower = A2(isBetween, + _U.chr("a"), + _U.chr("z")); + var isDigit = A2(isBetween, + _U.chr("0"), + _U.chr("9")); + var isOctDigit = A2(isBetween, + _U.chr("0"), + _U.chr("7")); + var isHexDigit = function ($char) { + return isDigit($char) || (A3(isBetween, + _U.chr("a"), + _U.chr("f"), + $char) || A3(isBetween, + _U.chr("A"), + _U.chr("F"), + $char)); + }; + _elm.Char.values = {_op: _op + ,isUpper: isUpper + ,isLower: isLower + ,isDigit: isDigit + ,isOctDigit: isOctDigit + ,isHexDigit: isHexDigit + ,toUpper: toUpper + ,toLower: toLower + ,toLocaleUpper: toLocaleUpper + ,toLocaleLower: toLocaleLower + ,toCode: toCode + ,fromCode: fromCode}; + return _elm.Char.values; +}; +Elm.Color = Elm.Color || {}; +Elm.Color.make = function (_elm) { + "use strict"; + _elm.Color = _elm.Color || {}; + if (_elm.Color.values) + return _elm.Color.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Color", + $Basics = Elm.Basics.make(_elm); + var Radial = F5(function (a, + b, + c, + d, + e) { + return {ctor: "Radial" + ,_0: a + ,_1: b + ,_2: c + ,_3: d + ,_4: e}; + }); + var radial = Radial; + var Linear = F3(function (a, + b, + c) { + return {ctor: "Linear" + ,_0: a + ,_1: b + ,_2: c}; + }); + var linear = Linear; + var fmod = F2(function (f,n) { + return function () { + var integer = $Basics.floor(f); + return $Basics.toFloat(A2($Basics._op["%"], + integer, + n)) + f - $Basics.toFloat(integer); + }(); + }); + var rgbToHsl = F3(function (red, + green, + blue) { + return function () { + var b = $Basics.toFloat(blue) / 255; + var g = $Basics.toFloat(green) / 255; + var r = $Basics.toFloat(red) / 255; + var cMax = A2($Basics.max, + A2($Basics.max,r,g), + b); + var cMin = A2($Basics.min, + A2($Basics.min,r,g), + b); + var c = cMax - cMin; + var lightness = (cMax + cMin) / 2; + var saturation = _U.eq(lightness, + 0) ? 0 : c / (1 - $Basics.abs(2 * lightness - 1)); + var hue = $Basics.degrees(60) * (_U.eq(cMax, + r) ? A2(fmod, + (g - b) / c, + 6) : _U.eq(cMax, + g) ? (b - r) / c + 2 : _U.eq(cMax, + b) ? (r - g) / c + 4 : _U.badIf($moduleName, + "between lines 150 and 152")); + return {ctor: "_Tuple3" + ,_0: hue + ,_1: saturation + ,_2: lightness}; + }(); + }); + var hslToRgb = F3(function (hue, + saturation, + lightness) { + return function () { + var hue$ = hue / $Basics.degrees(60); + var chroma = (1 - $Basics.abs(2 * lightness - 1)) * saturation; + var x = chroma * (1 - $Basics.abs(A2(fmod, + hue$, + 2) - 1)); + var $ = _U.cmp(hue$, + 0) < 0 ? {ctor: "_Tuple3" + ,_0: 0 + ,_1: 0 + ,_2: 0} : _U.cmp(hue$, + 1) < 0 ? {ctor: "_Tuple3" + ,_0: chroma + ,_1: x + ,_2: 0} : _U.cmp(hue$, + 2) < 0 ? {ctor: "_Tuple3" + ,_0: x + ,_1: chroma + ,_2: 0} : _U.cmp(hue$, + 3) < 0 ? {ctor: "_Tuple3" + ,_0: 0 + ,_1: chroma + ,_2: x} : _U.cmp(hue$, + 4) < 0 ? {ctor: "_Tuple3" + ,_0: 0 + ,_1: x + ,_2: chroma} : _U.cmp(hue$, + 5) < 0 ? {ctor: "_Tuple3" + ,_0: x + ,_1: 0 + ,_2: chroma} : _U.cmp(hue$, + 6) < 0 ? {ctor: "_Tuple3" + ,_0: chroma + ,_1: 0 + ,_2: x} : {ctor: "_Tuple3" + ,_0: 0 + ,_1: 0 + ,_2: 0}, + r = $._0, + g = $._1, + b = $._2; + var m = lightness - chroma / 2; + return {ctor: "_Tuple3" + ,_0: r + m + ,_1: g + m + ,_2: b + m}; + }(); + }); + var toRgb = function (color) { + return function () { + switch (color.ctor) + {case "HSLA": + return function () { + var $ = A3(hslToRgb, + color._0, + color._1, + color._2), + r = $._0, + g = $._1, + b = $._2; + return {_: {} + ,alpha: color._3 + ,blue: $Basics.round(255 * b) + ,green: $Basics.round(255 * g) + ,red: $Basics.round(255 * r)}; + }(); + case "RGBA": return {_: {} + ,alpha: color._3 + ,blue: color._2 + ,green: color._1 + ,red: color._0};} + _U.badCase($moduleName, + "between lines 124 and 132"); + }(); + }; + var toHsl = function (color) { + return function () { + switch (color.ctor) + {case "HSLA": return {_: {} + ,alpha: color._3 + ,hue: color._0 + ,lightness: color._2 + ,saturation: color._1}; + case "RGBA": + return function () { + var $ = A3(rgbToHsl, + color._0, + color._1, + color._2), + h = $._0, + s = $._1, + l = $._2; + return {_: {} + ,alpha: color._3 + ,hue: h + ,lightness: l + ,saturation: s}; + }();} + _U.badCase($moduleName, + "between lines 114 and 121"); + }(); + }; + var HSLA = F4(function (a, + b, + c, + d) { + return {ctor: "HSLA" + ,_0: a + ,_1: b + ,_2: c + ,_3: d}; + }); + var hsla = F4(function (hue, + saturation, + lightness, + alpha) { + return A4(HSLA, + hue - $Basics.turns($Basics.toFloat($Basics.floor(hue / (2 * $Basics.pi)))), + saturation, + lightness, + alpha); + }); + var hsl = F3(function (hue, + saturation, + lightness) { + return A4(hsla, + hue, + saturation, + lightness, + 1); + }); + var complement = function (color) { + return function () { + switch (color.ctor) + {case "HSLA": return A4(hsla, + color._0 + $Basics.degrees(180), + color._1, + color._2, + color._3); + case "RGBA": + return function () { + var $ = A3(rgbToHsl, + color._0, + color._1, + color._2), + h = $._0, + s = $._1, + l = $._2; + return A4(hsla, + h + $Basics.degrees(180), + s, + l, + color._3); + }();} + _U.badCase($moduleName, + "between lines 105 and 111"); + }(); + }; + var grayscale = function (p) { + return A4(HSLA,0,0,1 - p,1); + }; + var greyscale = function (p) { + return A4(HSLA,0,0,1 - p,1); + }; + var RGBA = F4(function (a, + b, + c, + d) { + return {ctor: "RGBA" + ,_0: a + ,_1: b + ,_2: c + ,_3: d}; + }); + var rgba = RGBA; + var rgb = F3(function (r,g,b) { + return A4(RGBA,r,g,b,1); + }); + var lightRed = A4(RGBA, + 239, + 41, + 41, + 1); + var red = A4(RGBA,204,0,0,1); + var darkRed = A4(RGBA, + 164, + 0, + 0, + 1); + var lightOrange = A4(RGBA, + 252, + 175, + 62, + 1); + var orange = A4(RGBA, + 245, + 121, + 0, + 1); + var darkOrange = A4(RGBA, + 206, + 92, + 0, + 1); + var lightYellow = A4(RGBA, + 255, + 233, + 79, + 1); + var yellow = A4(RGBA, + 237, + 212, + 0, + 1); + var darkYellow = A4(RGBA, + 196, + 160, + 0, + 1); + var lightGreen = A4(RGBA, + 138, + 226, + 52, + 1); + var green = A4(RGBA, + 115, + 210, + 22, + 1); + var darkGreen = A4(RGBA, + 78, + 154, + 6, + 1); + var lightBlue = A4(RGBA, + 114, + 159, + 207, + 1); + var blue = A4(RGBA, + 52, + 101, + 164, + 1); + var darkBlue = A4(RGBA, + 32, + 74, + 135, + 1); + var lightPurple = A4(RGBA, + 173, + 127, + 168, + 1); + var purple = A4(RGBA, + 117, + 80, + 123, + 1); + var darkPurple = A4(RGBA, + 92, + 53, + 102, + 1); + var lightBrown = A4(RGBA, + 233, + 185, + 110, + 1); + var brown = A4(RGBA, + 193, + 125, + 17, + 1); + var darkBrown = A4(RGBA, + 143, + 89, + 2, + 1); + var black = A4(RGBA,0,0,0,1); + var white = A4(RGBA, + 255, + 255, + 255, + 1); + var lightGrey = A4(RGBA, + 238, + 238, + 236, + 1); + var grey = A4(RGBA, + 211, + 215, + 207, + 1); + var darkGrey = A4(RGBA, + 186, + 189, + 182, + 1); + var lightGray = A4(RGBA, + 238, + 238, + 236, + 1); + var gray = A4(RGBA, + 211, + 215, + 207, + 1); + var darkGray = A4(RGBA, + 186, + 189, + 182, + 1); + var lightCharcoal = A4(RGBA, + 136, + 138, + 133, + 1); + var charcoal = A4(RGBA, + 85, + 87, + 83, + 1); + var darkCharcoal = A4(RGBA, + 46, + 52, + 54, + 1); + _elm.Color.values = {_op: _op + ,rgb: rgb + ,rgba: rgba + ,hsl: hsl + ,hsla: hsla + ,greyscale: greyscale + ,grayscale: grayscale + ,complement: complement + ,linear: linear + ,radial: radial + ,toRgb: toRgb + ,toHsl: toHsl + ,red: red + ,orange: orange + ,yellow: yellow + ,green: green + ,blue: blue + ,purple: purple + ,brown: brown + ,lightRed: lightRed + ,lightOrange: lightOrange + ,lightYellow: lightYellow + ,lightGreen: lightGreen + ,lightBlue: lightBlue + ,lightPurple: lightPurple + ,lightBrown: lightBrown + ,darkRed: darkRed + ,darkOrange: darkOrange + ,darkYellow: darkYellow + ,darkGreen: darkGreen + ,darkBlue: darkBlue + ,darkPurple: darkPurple + ,darkBrown: darkBrown + ,white: white + ,lightGrey: lightGrey + ,grey: grey + ,darkGrey: darkGrey + ,lightCharcoal: lightCharcoal + ,charcoal: charcoal + ,darkCharcoal: darkCharcoal + ,black: black + ,lightGray: lightGray + ,gray: gray + ,darkGray: darkGray}; + return _elm.Color.values; +}; +Elm.Debug = Elm.Debug || {}; +Elm.Debug.make = function (_elm) { + "use strict"; + _elm.Debug = _elm.Debug || {}; + if (_elm.Debug.values) + return _elm.Debug.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Debug", + $Graphics$Collage = Elm.Graphics.Collage.make(_elm), + $Native$Debug = Elm.Native.Debug.make(_elm); + var trace = $Native$Debug.tracePath; + var watchSummary = $Native$Debug.watchSummary; + var watch = $Native$Debug.watch; + var crash = $Native$Debug.crash; + var log = $Native$Debug.log; + _elm.Debug.values = {_op: _op + ,log: log + ,crash: crash + ,watch: watch + ,watchSummary: watchSummary + ,trace: trace}; + return _elm.Debug.values; +}; +Elm.Dict = Elm.Dict || {}; +Elm.Dict.make = function (_elm) { + "use strict"; + _elm.Dict = _elm.Dict || {}; + if (_elm.Dict.values) + return _elm.Dict.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Dict", + $Basics = Elm.Basics.make(_elm), + $List = Elm.List.make(_elm), + $Maybe = Elm.Maybe.make(_elm), + $Native$Debug = Elm.Native.Debug.make(_elm), + $String = Elm.String.make(_elm); + var foldr = F3(function (f, + acc, + t) { + return function () { + switch (t.ctor) + {case "RBEmpty": + switch (t._0.ctor) + {case "LBlack": return acc;} + break; + case "RBNode": return A3(foldr, + f, + A3(f, + t._1, + t._2, + A3(foldr,f,acc,t._4)), + t._3);} + _U.badCase($moduleName, + "between lines 408 and 416"); + }(); + }); + var keys = function (dict) { + return A3(foldr, + F3(function (key, + value, + keyList) { + return A2($List._op["::"], + key, + keyList); + }), + _L.fromArray([]), + dict); + }; + var values = function (dict) { + return A3(foldr, + F3(function (key, + value, + valueList) { + return A2($List._op["::"], + value, + valueList); + }), + _L.fromArray([]), + dict); + }; + var toList = function (dict) { + return A3(foldr, + F3(function (key,value,list) { + return A2($List._op["::"], + {ctor: "_Tuple2" + ,_0: key + ,_1: value}, + list); + }), + _L.fromArray([]), + dict); + }; + var foldl = F3(function (f, + acc, + dict) { + return function () { + switch (dict.ctor) + {case "RBEmpty": + switch (dict._0.ctor) + {case "LBlack": return acc;} + break; + case "RBNode": return A3(foldl, + f, + A3(f, + dict._1, + dict._2, + A3(foldl,f,acc,dict._3)), + dict._4);} + _U.badCase($moduleName, + "between lines 397 and 405"); + }(); + }); + var isBBlack = function (dict) { + return function () { + switch (dict.ctor) + {case "RBEmpty": + switch (dict._0.ctor) + {case "LBBlack": return true;} + break; + case "RBNode": + switch (dict._0.ctor) + {case "BBlack": return true;} + break;} + return false; + }(); + }; + var showFlag = function (f) { + return function () { + switch (f.ctor) + {case "Insert": return "Insert"; + case "Remove": return "Remove"; + case "Same": return "Same";} + _U.badCase($moduleName, + "between lines 173 and 179"); + }(); + }; + var Same = {ctor: "Same"}; + var Remove = {ctor: "Remove"}; + var Insert = {ctor: "Insert"}; + var get = F2(function (targetKey, + dict) { + return function () { + switch (dict.ctor) + {case "RBEmpty": + switch (dict._0.ctor) + {case "LBlack": + return $Maybe.Nothing;} + break; + case "RBNode": + return function () { + var _v29 = A2($Basics.compare, + targetKey, + dict._1); + switch (_v29.ctor) + {case "EQ": + return $Maybe.Just(dict._2); + case "GT": return A2(get, + targetKey, + dict._4); + case "LT": return A2(get, + targetKey, + dict._3);} + _U.badCase($moduleName, + "between lines 129 and 135"); + }();} + _U.badCase($moduleName, + "between lines 124 and 135"); + }(); + }); + var member = F2(function (key, + dict) { + return function () { + var _v30 = A2(get,key,dict); + switch (_v30.ctor) + {case "Just": return true; + case "Nothing": return false;} + _U.badCase($moduleName, + "between lines 138 and 140"); + }(); + }); + var max = function (dict) { + return function () { + switch (dict.ctor) + {case "RBEmpty": + return $Native$Debug.crash("(max Empty) is not defined"); + case "RBNode": + switch (dict._4.ctor) + {case "RBEmpty": + return {ctor: "_Tuple2" + ,_0: dict._1 + ,_1: dict._2};} + return max(dict._4);} + _U.badCase($moduleName, + "between lines 100 and 121"); + }(); + }; + var min = function (dict) { + return function () { + switch (dict.ctor) + {case "RBEmpty": + switch (dict._0.ctor) + {case "LBlack": + return $Native$Debug.crash("(min Empty) is not defined");} + break; + case "RBNode": + switch (dict._3.ctor) + {case "RBEmpty": + switch (dict._3._0.ctor) + {case "LBlack": + return {ctor: "_Tuple2" + ,_0: dict._1 + ,_1: dict._2};} + break;} + return min(dict._3);} + _U.badCase($moduleName, + "between lines 87 and 95"); + }(); + }; + var RBEmpty = function (a) { + return {ctor: "RBEmpty" + ,_0: a}; + }; + var RBNode = F5(function (a, + b, + c, + d, + e) { + return {ctor: "RBNode" + ,_0: a + ,_1: b + ,_2: c + ,_3: d + ,_4: e}; + }); + var showLColor = function (color) { + return function () { + switch (color.ctor) + {case "LBBlack": + return "LBBlack"; + case "LBlack": return "LBlack";} + _U.badCase($moduleName, + "between lines 70 and 72"); + }(); + }; + var LBBlack = {ctor: "LBBlack"}; + var LBlack = {ctor: "LBlack"}; + var empty = RBEmpty(LBlack); + var map = F2(function (f,dict) { + return function () { + switch (dict.ctor) + {case "RBEmpty": + switch (dict._0.ctor) + {case "LBlack": + return RBEmpty(LBlack);} + break; + case "RBNode": return A5(RBNode, + dict._0, + dict._1, + A2(f,dict._1,dict._2), + A2(map,f,dict._3), + A2(map,f,dict._4));} + _U.badCase($moduleName, + "between lines 385 and 394"); + }(); + }); + var showNColor = function (c) { + return function () { + switch (c.ctor) + {case "BBlack": return "BBlack"; + case "Black": return "Black"; + case "NBlack": return "NBlack"; + case "Red": return "Red";} + _U.badCase($moduleName, + "between lines 56 and 60"); + }(); + }; + var reportRemBug = F4(function (msg, + c, + lgot, + rgot) { + return $Native$Debug.crash($String.concat(_L.fromArray(["Internal red-black tree invariant violated, expected " + ,msg + ," and got " + ,showNColor(c) + ,"/" + ,lgot + ,"/" + ,rgot + ,"\nPlease report this bug to "]))); + }); + var NBlack = {ctor: "NBlack"}; + var BBlack = {ctor: "BBlack"}; + var Black = {ctor: "Black"}; + var ensureBlackRoot = function (dict) { + return function () { + switch (dict.ctor) + {case "RBEmpty": + switch (dict._0.ctor) + {case "LBlack": return dict;} + break; + case "RBNode": + switch (dict._0.ctor) + {case "Black": return dict; + case "Red": return A5(RBNode, + Black, + dict._1, + dict._2, + dict._3, + dict._4);} + break;} + _U.badCase($moduleName, + "between lines 145 and 157"); + }(); + }; + var blackish = function (t) { + return function () { + switch (t.ctor) + {case "RBEmpty": return true; + case "RBNode": + return _U.eq(t._0, + Black) || _U.eq(t._0,BBlack);} + _U.badCase($moduleName, + "between lines 330 and 332"); + }(); + }; + var blacken = function (t) { + return function () { + switch (t.ctor) + {case "RBEmpty": + return RBEmpty(LBlack); + case "RBNode": return A5(RBNode, + Black, + t._1, + t._2, + t._3, + t._4);} + _U.badCase($moduleName, + "between lines 369 and 371"); + }(); + }; + var Red = {ctor: "Red"}; + var moreBlack = function (color) { + return function () { + switch (color.ctor) + {case "BBlack": + return $Native$Debug.crash("Can\'t make a double black node more black!"); + case "Black": return BBlack; + case "NBlack": return Red; + case "Red": return Black;} + _U.badCase($moduleName, + "between lines 235 and 239"); + }(); + }; + var lessBlack = function (color) { + return function () { + switch (color.ctor) + {case "BBlack": return Black; + case "Black": return Red; + case "NBlack": + return $Native$Debug.crash("Can\'t make a negative black node less black!"); + case "Red": return NBlack;} + _U.badCase($moduleName, + "between lines 244 and 248"); + }(); + }; + var lessBlackTree = function (dict) { + return function () { + switch (dict.ctor) + {case "RBEmpty": + switch (dict._0.ctor) + {case "LBBlack": + return RBEmpty(LBlack);} + break; + case "RBNode": return A5(RBNode, + lessBlack(dict._0), + dict._1, + dict._2, + dict._3, + dict._4);} + _U.badCase($moduleName, + "between lines 253 and 255"); + }(); + }; + var redden = function (t) { + return function () { + switch (t.ctor) + {case "RBEmpty": + return $Native$Debug.crash("can\'t make a Leaf red"); + case "RBNode": return A5(RBNode, + Red, + t._1, + t._2, + t._3, + t._4);} + _U.badCase($moduleName, + "between lines 377 and 382"); + }(); + }; + var balance_node = function (t) { + return function () { + var assemble = function (col) { + return function (xk) { + return function (xv) { + return function (yk) { + return function (yv) { + return function (zk) { + return function (zv) { + return function (a) { + return function (b) { + return function (c) { + return function (d) { + return A5(RBNode, + lessBlack(col), + yk, + yv, + A5(RBNode,Black,xk,xv,a,b), + A5(RBNode,Black,zk,zv,c,d)); + }; + }; + }; + }; + }; + }; + }; + }; + }; + }; + }; + return blackish(t) ? function () { + switch (t.ctor) + {case "RBNode": + switch (t._3.ctor) + {case "RBNode": + switch (t._3._0.ctor) + {case "Red": + switch (t._3._3.ctor) + {case "RBNode": + switch (t._3._3._0.ctor) + {case "Red": + return assemble(t._0)(t._3._3._1)(t._3._3._2)(t._3._1)(t._3._2)(t._1)(t._2)(t._3._3._3)(t._3._3._4)(t._3._4)(t._4);} + break;} + switch (t._3._4.ctor) + {case "RBNode": + switch (t._3._4._0.ctor) + {case "Red": + return assemble(t._0)(t._3._1)(t._3._2)(t._3._4._1)(t._3._4._2)(t._1)(t._2)(t._3._3)(t._3._4._3)(t._3._4._4)(t._4);} + break;} + break;} + break;} + switch (t._4.ctor) + {case "RBNode": + switch (t._4._0.ctor) + {case "Red": + switch (t._4._3.ctor) + {case "RBNode": + switch (t._4._3._0.ctor) + {case "Red": + return assemble(t._0)(t._1)(t._2)(t._4._3._1)(t._4._3._2)(t._4._1)(t._4._2)(t._3)(t._4._3._3)(t._4._3._4)(t._4._4);} + break;} + switch (t._4._4.ctor) + {case "RBNode": + switch (t._4._4._0.ctor) + {case "Red": + return assemble(t._0)(t._1)(t._2)(t._4._1)(t._4._2)(t._4._4._1)(t._4._4._2)(t._3)(t._4._3)(t._4._4._3)(t._4._4._4);} + break;} + break;} + break;} + switch (t._0.ctor) + {case "BBlack": + switch (t._4.ctor) + {case "RBNode": + switch (t._4._0.ctor) + {case "NBlack": + switch (t._4._3.ctor) + {case "RBNode": + switch (t._4._3._0.ctor) + {case "Black": + return function () { + switch (t._4._4.ctor) + {case "RBNode": + switch (t._4._4._0.ctor) + {case "Black": + return A5(RBNode, + Black, + t._4._3._1, + t._4._3._2, + A5(RBNode, + Black, + t._1, + t._2, + t._3, + t._4._3._3), + A5(balance, + Black, + t._4._1, + t._4._2, + t._4._3._4, + redden(t._4._4)));} + break;} + return t; + }();} + break;} + break;} + break;} + switch (t._3.ctor) + {case "RBNode": + switch (t._3._0.ctor) + {case "NBlack": + switch (t._3._4.ctor) + {case "RBNode": + switch (t._3._4._0.ctor) + {case "Black": + return function () { + switch (t._3._3.ctor) + {case "RBNode": + switch (t._3._3._0.ctor) + {case "Black": + return A5(RBNode, + Black, + t._3._4._1, + t._3._4._2, + A5(balance, + Black, + t._3._1, + t._3._2, + redden(t._3._3), + t._3._4._3), + A5(RBNode, + Black, + t._1, + t._2, + t._3._4._4, + t._4));} + break;} + return t; + }();} + break;} + break;} + break;} + break;} + break;} + return t; + }() : t; + }(); + }; + var balance = F5(function (c, + k, + v, + l, + r) { + return balance_node(A5(RBNode, + c, + k, + v, + l, + r)); + }); + var bubble = F5(function (c, + k, + v, + l, + r) { + return isBBlack(l) || isBBlack(r) ? A5(balance, + moreBlack(c), + k, + v, + lessBlackTree(l), + lessBlackTree(r)) : A5(RBNode, + c, + k, + v, + l, + r); + }); + var remove_max = F5(function (c, + k, + v, + l, + r) { + return function () { + switch (r.ctor) + {case "RBEmpty": return A3(rem, + c, + l, + r); + case "RBNode": return A5(bubble, + c, + k, + v, + l, + A5(remove_max, + r._0, + r._1, + r._2, + r._3, + r._4));} + _U.badCase($moduleName, + "between lines 314 and 319"); + }(); + }); + var rem = F3(function (c,l,r) { + return function () { + var _v169 = {ctor: "_Tuple2" + ,_0: l + ,_1: r}; + switch (_v169.ctor) + {case "_Tuple2": + switch (_v169._0.ctor) + {case "RBEmpty": + switch (_v169._1.ctor) + {case "RBEmpty": + return function () { + switch (c.ctor) + {case "Black": + return RBEmpty(LBBlack); + case "Red": + return RBEmpty(LBlack);} + _U.badCase($moduleName, + "between lines 273 and 277"); + }(); + case "RBNode": + return function () { + var _v191 = {ctor: "_Tuple3" + ,_0: c + ,_1: _v169._0._0 + ,_2: _v169._1._0}; + switch (_v191.ctor) + {case "_Tuple3": + switch (_v191._0.ctor) + {case "Black": + switch (_v191._1.ctor) + {case "LBlack": + switch (_v191._2.ctor) + {case "Red": return A5(RBNode, + Black, + _v169._1._1, + _v169._1._2, + _v169._1._3, + _v169._1._4);} + break;} + break;} + break;} + return A4(reportRemBug, + "Black/LBlack/Red", + c, + showLColor(_v169._0._0), + showNColor(_v169._1._0)); + }();} + break; + case "RBNode": + switch (_v169._1.ctor) + {case "RBEmpty": + return function () { + var _v195 = {ctor: "_Tuple3" + ,_0: c + ,_1: _v169._0._0 + ,_2: _v169._1._0}; + switch (_v195.ctor) + {case "_Tuple3": + switch (_v195._0.ctor) + {case "Black": + switch (_v195._1.ctor) + {case "Red": + switch (_v195._2.ctor) + {case "LBlack": + return A5(RBNode, + Black, + _v169._0._1, + _v169._0._2, + _v169._0._3, + _v169._0._4);} + break;} + break;} + break;} + return A4(reportRemBug, + "Black/Red/LBlack", + c, + showNColor(_v169._0._0), + showLColor(_v169._1._0)); + }(); + case "RBNode": + return function () { + var l$ = A5(remove_max, + _v169._0._0, + _v169._0._1, + _v169._0._2, + _v169._0._3, + _v169._0._4); + var r = A5(RBNode, + _v169._1._0, + _v169._1._1, + _v169._1._2, + _v169._1._3, + _v169._1._4); + var l = A5(RBNode, + _v169._0._0, + _v169._0._1, + _v169._0._2, + _v169._0._3, + _v169._0._4); + var $ = max(l), + k = $._0, + v = $._1; + return A5(bubble,c,k,v,l$,r); + }();} + break;} + break;} + _U.badCase($moduleName, + "between lines 271 and 300"); + }(); + }); + var update = F3(function (k, + alter, + dict) { + return function () { + var up = function (dict) { + return function () { + switch (dict.ctor) + {case "RBEmpty": + switch (dict._0.ctor) + {case "LBlack": + return function () { + var _v206 = alter($Maybe.Nothing); + switch (_v206.ctor) + {case "Just": + return {ctor: "_Tuple2" + ,_0: Insert + ,_1: A5(RBNode, + Red, + k, + _v206._0, + empty, + empty)}; + case "Nothing": + return {ctor: "_Tuple2" + ,_0: Same + ,_1: empty};} + _U.badCase($moduleName, + "between lines 185 and 189"); + }();} + break; + case "RBNode": + return function () { + var _v208 = A2($Basics.compare, + k, + dict._1); + switch (_v208.ctor) + {case "EQ": return function () { + var _v209 = alter($Maybe.Just(dict._2)); + switch (_v209.ctor) + {case "Just": + return {ctor: "_Tuple2" + ,_0: Same + ,_1: A5(RBNode, + dict._0, + dict._1, + _v209._0, + dict._3, + dict._4)}; + case "Nothing": + return {ctor: "_Tuple2" + ,_0: Remove + ,_1: A3(rem, + dict._0, + dict._3, + dict._4)};} + _U.badCase($moduleName, + "between lines 192 and 197"); + }(); + case "GT": return function () { + var $ = up(dict._4), + flag = $._0, + newRight = $._1; + return function () { + switch (flag.ctor) + {case "Insert": + return {ctor: "_Tuple2" + ,_0: Insert + ,_1: A5(balance, + dict._0, + dict._1, + dict._2, + dict._3, + newRight)}; + case "Remove": + return {ctor: "_Tuple2" + ,_0: Remove + ,_1: A5(bubble, + dict._0, + dict._1, + dict._2, + dict._3, + newRight)}; + case "Same": + return {ctor: "_Tuple2" + ,_0: Same + ,_1: A5(RBNode, + dict._0, + dict._1, + dict._2, + dict._3, + newRight)};} + _U.badCase($moduleName, + "between lines 206 and 211"); + }(); + }(); + case "LT": return function () { + var $ = up(dict._3), + flag = $._0, + newLeft = $._1; + return function () { + switch (flag.ctor) + {case "Insert": + return {ctor: "_Tuple2" + ,_0: Insert + ,_1: A5(balance, + dict._0, + dict._1, + dict._2, + newLeft, + dict._4)}; + case "Remove": + return {ctor: "_Tuple2" + ,_0: Remove + ,_1: A5(bubble, + dict._0, + dict._1, + dict._2, + newLeft, + dict._4)}; + case "Same": + return {ctor: "_Tuple2" + ,_0: Same + ,_1: A5(RBNode, + dict._0, + dict._1, + dict._2, + newLeft, + dict._4)};} + _U.badCase($moduleName, + "between lines 199 and 204"); + }(); + }();} + _U.badCase($moduleName, + "between lines 190 and 211"); + }();} + _U.badCase($moduleName, + "between lines 183 and 211"); + }(); + }; + var $ = up(dict), + flag = $._0, + updatedDict = $._1; + return function () { + switch (flag.ctor) + {case "Insert": + return ensureBlackRoot(updatedDict); + case "Remove": + return blacken(updatedDict); + case "Same": + return updatedDict;} + _U.badCase($moduleName, + "between lines 213 and 219"); + }(); + }(); + }); + var insert = F3(function (key, + value, + dict) { + return A3(update, + key, + $Basics.always($Maybe.Just(value)), + dict); + }); + var singleton = F2(function (key, + value) { + return A3(insert, + key, + value, + RBEmpty(LBlack)); + }); + var union = F2(function (t1, + t2) { + return A3(foldl, + insert, + t2, + t1); + }); + var fromList = function (assocs) { + return A3($List.foldl, + F2(function (_v214,dict) { + return function () { + switch (_v214.ctor) + {case "_Tuple2": + return A3(insert, + _v214._0, + _v214._1, + dict);} + _U.badCase($moduleName, + "on line 457, column 38 to 59"); + }(); + }), + empty, + assocs); + }; + var filter = F2(function (predicate, + dictionary) { + return function () { + var add = F3(function (key, + value, + dict) { + return A2(predicate, + key, + value) ? A3(insert, + key, + value, + dict) : dict; + }); + return A3(foldl, + add, + empty, + dictionary); + }(); + }); + var intersect = F2(function (t1, + t2) { + return A2(filter, + F2(function (k,_v218) { + return function () { + return A2(member,k,t2); + }(); + }), + t1); + }); + var partition = F2(function (predicate, + dict) { + return function () { + var add = F3(function (key, + value, + _v220) { + return function () { + switch (_v220.ctor) + {case "_Tuple2": + return A2(predicate, + key, + value) ? {ctor: "_Tuple2" + ,_0: A3(insert, + key, + value, + _v220._0) + ,_1: _v220._1} : {ctor: "_Tuple2" + ,_0: _v220._0 + ,_1: A3(insert, + key, + value, + _v220._1)};} + _U.badCase($moduleName, + "between lines 478 and 480"); + }(); + }); + return A3(foldl, + add, + {ctor: "_Tuple2" + ,_0: empty + ,_1: empty}, + dict); + }(); + }); + var remove = F2(function (key, + dict) { + return A3(update, + key, + $Basics.always($Maybe.Nothing), + dict); + }); + var diff = F2(function (t1,t2) { + return A3(foldl, + F3(function (k,v,t) { + return A2(remove,k,t); + }), + t1, + t2); + }); + _elm.Dict.values = {_op: _op + ,empty: empty + ,singleton: singleton + ,insert: insert + ,update: update + ,get: get + ,remove: remove + ,member: member + ,filter: filter + ,partition: partition + ,foldl: foldl + ,foldr: foldr + ,map: map + ,union: union + ,intersect: intersect + ,diff: diff + ,keys: keys + ,values: values + ,toList: toList + ,fromList: fromList}; + return _elm.Dict.values; +}; +Elm.Graphics = Elm.Graphics || {}; +Elm.Graphics.Collage = Elm.Graphics.Collage || {}; +Elm.Graphics.Collage.make = function (_elm) { + "use strict"; + _elm.Graphics = _elm.Graphics || {}; + _elm.Graphics.Collage = _elm.Graphics.Collage || {}; + if (_elm.Graphics.Collage.values) + return _elm.Graphics.Collage.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Graphics.Collage", + $Basics = Elm.Basics.make(_elm), + $Color = Elm.Color.make(_elm), + $Graphics$Element = Elm.Graphics.Element.make(_elm), + $List = Elm.List.make(_elm), + $Native$Graphics$Collage = Elm.Native.Graphics.Collage.make(_elm), + $Text = Elm.Text.make(_elm), + $Transform2D = Elm.Transform2D.make(_elm); + var ngon = F2(function (n,r) { + return function () { + var m = $Basics.toFloat(n); + var t = 2 * $Basics.pi / m; + var f = function (i) { + return {ctor: "_Tuple2" + ,_0: r * $Basics.cos(t * i) + ,_1: r * $Basics.sin(t * i)}; + }; + return A2($List.map, + f, + _L.range(0,m - 1)); + }(); + }); + var oval = F2(function (w,h) { + return function () { + var hh = h / 2; + var hw = w / 2; + var n = 50; + var t = 2 * $Basics.pi / n; + var f = function (i) { + return {ctor: "_Tuple2" + ,_0: hw * $Basics.cos(t * i) + ,_1: hh * $Basics.sin(t * i)}; + }; + return A2($List.map, + f, + _L.range(0,n - 1)); + }(); + }); + var circle = function (r) { + return A2(oval,2 * r,2 * r); + }; + var rect = F2(function (w,h) { + return function () { + var hh = h / 2; + var hw = w / 2; + return _L.fromArray([{ctor: "_Tuple2" + ,_0: 0 - hw + ,_1: 0 - hh} + ,{ctor: "_Tuple2" + ,_0: 0 - hw + ,_1: hh} + ,{ctor: "_Tuple2",_0: hw,_1: hh} + ,{ctor: "_Tuple2" + ,_0: hw + ,_1: 0 - hh}]); + }(); + }); + var square = function (n) { + return A2(rect,n,n); + }; + var polygon = function (points) { + return points; + }; + var segment = F2(function (p1, + p2) { + return _L.fromArray([p1,p2]); + }); + var path = function (ps) { + return ps; + }; + var collage = $Native$Graphics$Collage.collage; + var alpha = F2(function (a,f) { + return _U.replace([["alpha" + ,a]], + f); + }); + var rotate = F2(function (t,f) { + return _U.replace([["theta" + ,f.theta + t]], + f); + }); + var scale = F2(function (s,f) { + return _U.replace([["scale" + ,f.scale * s]], + f); + }); + var moveY = F2(function (y,f) { + return _U.replace([["y" + ,f.y + y]], + f); + }); + var moveX = F2(function (x,f) { + return _U.replace([["x" + ,f.x + x]], + f); + }); + var move = F2(function (_v0,f) { + return function () { + switch (_v0.ctor) + {case "_Tuple2": + return _U.replace([["x" + ,f.x + _v0._0] + ,["y",f.y + _v0._1]], + f);} + _U.badCase($moduleName, + "on line 226, column 7 to 35"); + }(); + }); + var form = function (f) { + return {_: {} + ,alpha: 1 + ,form: f + ,scale: 1 + ,theta: 0 + ,x: 0 + ,y: 0}; + }; + var Fill = function (a) { + return {ctor: "Fill",_0: a}; + }; + var Line = function (a) { + return {ctor: "Line",_0: a}; + }; + var FGroup = F2(function (a,b) { + return {ctor: "FGroup" + ,_0: a + ,_1: b}; + }); + var group = function (fs) { + return form(A2(FGroup, + $Transform2D.identity, + fs)); + }; + var groupTransform = F2(function (matrix, + fs) { + return form(A2(FGroup, + matrix, + fs)); + }); + var FElement = function (a) { + return {ctor: "FElement" + ,_0: a}; + }; + var toForm = function (e) { + return form(FElement(e)); + }; + var FImage = F4(function (a, + b, + c, + d) { + return {ctor: "FImage" + ,_0: a + ,_1: b + ,_2: c + ,_3: d}; + }); + var sprite = F4(function (w, + h, + pos, + src) { + return form(A4(FImage, + w, + h, + pos, + src)); + }); + var FText = function (a) { + return {ctor: "FText",_0: a}; + }; + var text = function (t) { + return form(FText(t)); + }; + var FOutlinedText = F2(function (a, + b) { + return {ctor: "FOutlinedText" + ,_0: a + ,_1: b}; + }); + var outlinedText = F2(function (ls, + t) { + return form(A2(FOutlinedText, + ls, + t)); + }); + var FShape = F2(function (a,b) { + return {ctor: "FShape" + ,_0: a + ,_1: b}; + }); + var fill = F2(function (style, + shape) { + return form(A2(FShape, + Fill(style), + shape)); + }); + var outlined = F2(function (style, + shape) { + return form(A2(FShape, + Line(style), + shape)); + }); + var FPath = F2(function (a,b) { + return {ctor: "FPath" + ,_0: a + ,_1: b}; + }); + var traced = F2(function (style, + path) { + return form(A2(FPath, + style, + path)); + }); + var LineStyle = F6(function (a, + b, + c, + d, + e, + f) { + return {_: {} + ,cap: c + ,color: a + ,dashOffset: f + ,dashing: e + ,join: d + ,width: b}; + }); + var Clipped = {ctor: "Clipped"}; + var Sharp = function (a) { + return {ctor: "Sharp",_0: a}; + }; + var Smooth = {ctor: "Smooth"}; + var Padded = {ctor: "Padded"}; + var Round = {ctor: "Round"}; + var Flat = {ctor: "Flat"}; + var defaultLine = {_: {} + ,cap: Flat + ,color: $Color.black + ,dashOffset: 0 + ,dashing: _L.fromArray([]) + ,join: Sharp(10) + ,width: 1}; + var solid = function (clr) { + return _U.replace([["color" + ,clr]], + defaultLine); + }; + var dashed = function (clr) { + return _U.replace([["color" + ,clr] + ,["dashing" + ,_L.fromArray([8,4])]], + defaultLine); + }; + var dotted = function (clr) { + return _U.replace([["color" + ,clr] + ,["dashing" + ,_L.fromArray([3,3])]], + defaultLine); + }; + var Grad = function (a) { + return {ctor: "Grad",_0: a}; + }; + var gradient = F2(function (grad, + shape) { + return A2(fill, + Grad(grad), + shape); + }); + var Texture = function (a) { + return {ctor: "Texture" + ,_0: a}; + }; + var textured = F2(function (src, + shape) { + return A2(fill, + Texture(src), + shape); + }); + var Solid = function (a) { + return {ctor: "Solid",_0: a}; + }; + var filled = F2(function (color, + shape) { + return A2(fill, + Solid(color), + shape); + }); + var Form = F6(function (a, + b, + c, + d, + e, + f) { + return {_: {} + ,alpha: e + ,form: f + ,scale: b + ,theta: a + ,x: c + ,y: d}; + }); + _elm.Graphics.Collage.values = {_op: _op + ,collage: collage + ,toForm: toForm + ,filled: filled + ,textured: textured + ,gradient: gradient + ,outlined: outlined + ,traced: traced + ,text: text + ,outlinedText: outlinedText + ,move: move + ,moveX: moveX + ,moveY: moveY + ,scale: scale + ,rotate: rotate + ,alpha: alpha + ,group: group + ,groupTransform: groupTransform + ,rect: rect + ,oval: oval + ,square: square + ,circle: circle + ,ngon: ngon + ,polygon: polygon + ,segment: segment + ,path: path + ,solid: solid + ,dashed: dashed + ,dotted: dotted + ,defaultLine: defaultLine + ,Form: Form + ,LineStyle: LineStyle + ,Flat: Flat + ,Round: Round + ,Padded: Padded + ,Smooth: Smooth + ,Sharp: Sharp + ,Clipped: Clipped}; + return _elm.Graphics.Collage.values; +}; +Elm.Graphics = Elm.Graphics || {}; +Elm.Graphics.Element = Elm.Graphics.Element || {}; +Elm.Graphics.Element.make = function (_elm) { + "use strict"; + _elm.Graphics = _elm.Graphics || {}; + _elm.Graphics.Element = _elm.Graphics.Element || {}; + if (_elm.Graphics.Element.values) + return _elm.Graphics.Element.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Graphics.Element", + $Basics = Elm.Basics.make(_elm), + $Color = Elm.Color.make(_elm), + $List = Elm.List.make(_elm), + $Maybe = Elm.Maybe.make(_elm), + $Native$Graphics$Element = Elm.Native.Graphics.Element.make(_elm), + $Text = Elm.Text.make(_elm); + var DOut = {ctor: "DOut"}; + var outward = DOut; + var DIn = {ctor: "DIn"}; + var inward = DIn; + var DRight = {ctor: "DRight"}; + var right = DRight; + var DLeft = {ctor: "DLeft"}; + var left = DLeft; + var DDown = {ctor: "DDown"}; + var down = DDown; + var DUp = {ctor: "DUp"}; + var up = DUp; + var Position = F4(function (a, + b, + c, + d) { + return {_: {} + ,horizontal: a + ,vertical: b + ,x: c + ,y: d}; + }); + var Relative = function (a) { + return {ctor: "Relative" + ,_0: a}; + }; + var relative = Relative; + var Absolute = function (a) { + return {ctor: "Absolute" + ,_0: a}; + }; + var absolute = Absolute; + var N = {ctor: "N"}; + var bottomLeftAt = F2(function (x, + y) { + return {_: {} + ,horizontal: N + ,vertical: N + ,x: x + ,y: y}; + }); + var Z = {ctor: "Z"}; + var middle = {_: {} + ,horizontal: Z + ,vertical: Z + ,x: Relative(0.5) + ,y: Relative(0.5)}; + var midLeft = _U.replace([["horizontal" + ,N] + ,["x",Absolute(0)]], + middle); + var middleAt = F2(function (x, + y) { + return {_: {} + ,horizontal: Z + ,vertical: Z + ,x: x + ,y: y}; + }); + var midLeftAt = F2(function (x, + y) { + return {_: {} + ,horizontal: N + ,vertical: Z + ,x: x + ,y: y}; + }); + var midBottomAt = F2(function (x, + y) { + return {_: {} + ,horizontal: Z + ,vertical: N + ,x: x + ,y: y}; + }); + var P = {ctor: "P"}; + var topLeft = {_: {} + ,horizontal: N + ,vertical: P + ,x: Absolute(0) + ,y: Absolute(0)}; + var bottomLeft = _U.replace([["vertical" + ,N]], + topLeft); + var topRight = _U.replace([["horizontal" + ,P]], + topLeft); + var bottomRight = _U.replace([["horizontal" + ,P]], + bottomLeft); + var midRight = _U.replace([["horizontal" + ,P]], + midLeft); + var midTop = _U.replace([["vertical" + ,P] + ,["y",Absolute(0)]], + middle); + var midBottom = _U.replace([["vertical" + ,N]], + midTop); + var topLeftAt = F2(function (x, + y) { + return {_: {} + ,horizontal: N + ,vertical: P + ,x: x + ,y: y}; + }); + var topRightAt = F2(function (x, + y) { + return {_: {} + ,horizontal: P + ,vertical: P + ,x: x + ,y: y}; + }); + var bottomRightAt = F2(function (x, + y) { + return {_: {} + ,horizontal: P + ,vertical: N + ,x: x + ,y: y}; + }); + var midRightAt = F2(function (x, + y) { + return {_: {} + ,horizontal: P + ,vertical: Z + ,x: x + ,y: y}; + }); + var midTopAt = F2(function (x, + y) { + return {_: {} + ,horizontal: Z + ,vertical: P + ,x: x + ,y: y}; + }); + var justified = $Native$Graphics$Element.block("justify"); + var centered = $Native$Graphics$Element.block("center"); + var rightAligned = $Native$Graphics$Element.block("right"); + var leftAligned = $Native$Graphics$Element.block("left"); + var show = function (value) { + return leftAligned($Text.monospace($Text.fromString($Basics.toString(value)))); + }; + var Tiled = {ctor: "Tiled"}; + var Cropped = function (a) { + return {ctor: "Cropped" + ,_0: a}; + }; + var Fitted = {ctor: "Fitted"}; + var Plain = {ctor: "Plain"}; + var Custom = {ctor: "Custom"}; + var RawHtml = {ctor: "RawHtml"}; + var Spacer = {ctor: "Spacer"}; + var Flow = F2(function (a,b) { + return {ctor: "Flow" + ,_0: a + ,_1: b}; + }); + var Container = F2(function (a, + b) { + return {ctor: "Container" + ,_0: a + ,_1: b}; + }); + var Image = F4(function (a, + b, + c, + d) { + return {ctor: "Image" + ,_0: a + ,_1: b + ,_2: c + ,_3: d}; + }); + var newElement = $Native$Graphics$Element.newElement; + var image = F3(function (w, + h, + src) { + return A3(newElement, + w, + h, + A4(Image,Plain,w,h,src)); + }); + var fittedImage = F3(function (w, + h, + src) { + return A3(newElement, + w, + h, + A4(Image,Fitted,w,h,src)); + }); + var croppedImage = F4(function (pos, + w, + h, + src) { + return A3(newElement, + w, + h, + A4(Image,Cropped(pos),w,h,src)); + }); + var tiledImage = F3(function (w, + h, + src) { + return A3(newElement, + w, + h, + A4(Image,Tiled,w,h,src)); + }); + var container = F4(function (w, + h, + pos, + e) { + return A3(newElement, + w, + h, + A2(Container,pos,e)); + }); + var spacer = F2(function (w,h) { + return A3(newElement, + w, + h, + Spacer); + }); + var link = F2(function (href, + e) { + return function () { + var p = e.props; + return {_: {} + ,element: e.element + ,props: _U.replace([["href" + ,href]], + p)}; + }(); + }); + var tag = F2(function (name,e) { + return function () { + var p = e.props; + return {_: {} + ,element: e.element + ,props: _U.replace([["tag" + ,name]], + p)}; + }(); + }); + var color = F2(function (c,e) { + return function () { + var p = e.props; + return {_: {} + ,element: e.element + ,props: _U.replace([["color" + ,$Maybe.Just(c)]], + p)}; + }(); + }); + var opacity = F2(function (o, + e) { + return function () { + var p = e.props; + return {_: {} + ,element: e.element + ,props: _U.replace([["opacity" + ,o]], + p)}; + }(); + }); + var height = F2(function (nh, + e) { + return function () { + var p = e.props; + var props = function () { + var _v0 = e.element; + switch (_v0.ctor) + {case "Image": + return _U.replace([["width" + ,$Basics.round($Basics.toFloat(_v0._1) / $Basics.toFloat(_v0._2) * $Basics.toFloat(nh))]], + p);} + return p; + }(); + return {_: {} + ,element: e.element + ,props: _U.replace([["height" + ,nh]], + p)}; + }(); + }); + var width = F2(function (nw,e) { + return function () { + var p = e.props; + var props = function () { + var _v5 = e.element; + switch (_v5.ctor) + {case "Image": + return _U.replace([["height" + ,$Basics.round($Basics.toFloat(_v5._2) / $Basics.toFloat(_v5._1) * $Basics.toFloat(nw))]], + p); + case "RawHtml": + return _U.replace([["height" + ,$Basics.snd(A2($Native$Graphics$Element.htmlHeight, + nw, + e.element))]], + p);} + return p; + }(); + return {_: {} + ,element: e.element + ,props: _U.replace([["width" + ,nw]], + props)}; + }(); + }); + var size = F3(function (w,h,e) { + return A2(height, + h, + A2(width,w,e)); + }); + var sizeOf = function (e) { + return {ctor: "_Tuple2" + ,_0: e.props.width + ,_1: e.props.height}; + }; + var heightOf = function (e) { + return e.props.height; + }; + var widthOf = function (e) { + return e.props.width; + }; + var above = F2(function (hi, + lo) { + return A3(newElement, + A2($Basics.max, + widthOf(hi), + widthOf(lo)), + heightOf(hi) + heightOf(lo), + A2(Flow, + DDown, + _L.fromArray([hi,lo]))); + }); + var below = F2(function (lo, + hi) { + return A3(newElement, + A2($Basics.max, + widthOf(hi), + widthOf(lo)), + heightOf(hi) + heightOf(lo), + A2(Flow, + DDown, + _L.fromArray([hi,lo]))); + }); + var beside = F2(function (lft, + rht) { + return A3(newElement, + widthOf(lft) + widthOf(rht), + A2($Basics.max, + heightOf(lft), + heightOf(rht)), + A2(Flow, + right, + _L.fromArray([lft,rht]))); + }); + var layers = function (es) { + return function () { + var hs = A2($List.map, + heightOf, + es); + var ws = A2($List.map, + widthOf, + es); + return A3(newElement, + A2($Maybe.withDefault, + 0, + $List.maximum(ws)), + A2($Maybe.withDefault, + 0, + $List.maximum(hs)), + A2(Flow,DOut,es)); + }(); + }; + var empty = A2(spacer,0,0); + var flow = F2(function (dir, + es) { + return function () { + var newFlow = F2(function (w, + h) { + return A3(newElement, + w, + h, + A2(Flow,dir,es)); + }); + var maxOrZero = function (list) { + return A2($Maybe.withDefault, + 0, + $List.maximum(list)); + }; + var hs = A2($List.map, + heightOf, + es); + var ws = A2($List.map, + widthOf, + es); + return _U.eq(es, + _L.fromArray([])) ? empty : function () { + switch (dir.ctor) + {case "DDown": + return A2(newFlow, + maxOrZero(ws), + $List.sum(hs)); + case "DIn": return A2(newFlow, + maxOrZero(ws), + maxOrZero(hs)); + case "DLeft": return A2(newFlow, + $List.sum(ws), + maxOrZero(hs)); + case "DOut": return A2(newFlow, + maxOrZero(ws), + maxOrZero(hs)); + case "DRight": + return A2(newFlow, + $List.sum(ws), + maxOrZero(hs)); + case "DUp": return A2(newFlow, + maxOrZero(ws), + $List.sum(hs));} + _U.badCase($moduleName, + "between lines 362 and 373"); + }(); + }(); + }); + var Properties = F9(function (a, + b, + c, + d, + e, + f, + g, + h, + i) { + return {_: {} + ,click: i + ,color: e + ,height: c + ,hover: h + ,href: f + ,id: a + ,opacity: d + ,tag: g + ,width: b}; + }); + var Element = F2(function (a, + b) { + return {_: {} + ,element: b + ,props: a}; + }); + _elm.Graphics.Element.values = {_op: _op + ,image: image + ,fittedImage: fittedImage + ,croppedImage: croppedImage + ,tiledImage: tiledImage + ,leftAligned: leftAligned + ,rightAligned: rightAligned + ,centered: centered + ,justified: justified + ,show: show + ,width: width + ,height: height + ,size: size + ,color: color + ,opacity: opacity + ,link: link + ,tag: tag + ,widthOf: widthOf + ,heightOf: heightOf + ,sizeOf: sizeOf + ,flow: flow + ,up: up + ,down: down + ,left: left + ,right: right + ,inward: inward + ,outward: outward + ,layers: layers + ,above: above + ,below: below + ,beside: beside + ,empty: empty + ,spacer: spacer + ,container: container + ,middle: middle + ,midTop: midTop + ,midBottom: midBottom + ,midLeft: midLeft + ,midRight: midRight + ,topLeft: topLeft + ,topRight: topRight + ,bottomLeft: bottomLeft + ,bottomRight: bottomRight + ,absolute: absolute + ,relative: relative + ,middleAt: middleAt + ,midTopAt: midTopAt + ,midBottomAt: midBottomAt + ,midLeftAt: midLeftAt + ,midRightAt: midRightAt + ,topLeftAt: topLeftAt + ,topRightAt: topRightAt + ,bottomLeftAt: bottomLeftAt + ,bottomRightAt: bottomRightAt + ,Element: Element + ,Position: Position}; + return _elm.Graphics.Element.values; +}; +Elm.Graphics = Elm.Graphics || {}; +Elm.Graphics.Input = Elm.Graphics.Input || {}; +Elm.Graphics.Input.make = function (_elm) { + "use strict"; + _elm.Graphics = _elm.Graphics || {}; + _elm.Graphics.Input = _elm.Graphics.Input || {}; + if (_elm.Graphics.Input.values) + return _elm.Graphics.Input.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Graphics.Input", + $Graphics$Element = Elm.Graphics.Element.make(_elm), + $Native$Graphics$Input = Elm.Native.Graphics.Input.make(_elm), + $Signal = Elm.Signal.make(_elm); + var clickable = $Native$Graphics$Input.clickable; + var hoverable = $Native$Graphics$Input.hoverable; + var dropDown = $Native$Graphics$Input.dropDown; + var checkbox = $Native$Graphics$Input.checkbox; + var customButton = $Native$Graphics$Input.customButton; + var button = $Native$Graphics$Input.button; + _elm.Graphics.Input.values = {_op: _op + ,button: button + ,customButton: customButton + ,checkbox: checkbox + ,dropDown: dropDown + ,hoverable: hoverable + ,clickable: clickable}; + return _elm.Graphics.Input.values; +}; +Elm.Graphics = Elm.Graphics || {}; +Elm.Graphics.Input = Elm.Graphics.Input || {}; +Elm.Graphics.Input.Field = Elm.Graphics.Input.Field || {}; +Elm.Graphics.Input.Field.make = function (_elm) { + "use strict"; + _elm.Graphics = _elm.Graphics || {}; + _elm.Graphics.Input = _elm.Graphics.Input || {}; + _elm.Graphics.Input.Field = _elm.Graphics.Input.Field || {}; + if (_elm.Graphics.Input.Field.values) + return _elm.Graphics.Input.Field.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Graphics.Input.Field", + $Color = Elm.Color.make(_elm), + $Graphics$Element = Elm.Graphics.Element.make(_elm), + $Native$Graphics$Input = Elm.Native.Graphics.Input.make(_elm), + $Signal = Elm.Signal.make(_elm), + $Text = Elm.Text.make(_elm); + var email = $Native$Graphics$Input.email; + var password = $Native$Graphics$Input.password; + var field = $Native$Graphics$Input.field; + var Backward = {ctor: "Backward"}; + var Forward = {ctor: "Forward"}; + var Selection = F3(function (a, + b, + c) { + return {_: {} + ,direction: c + ,end: b + ,start: a}; + }); + var Content = F2(function (a, + b) { + return {_: {} + ,selection: b + ,string: a}; + }); + var noContent = A2(Content, + "", + A3(Selection,0,0,Forward)); + var Style = F4(function (a, + b, + c, + d) { + return {_: {} + ,highlight: c + ,outline: b + ,padding: a + ,style: d}; + }); + var Highlight = F2(function (a, + b) { + return {_: {} + ,color: a + ,width: b}; + }); + var noHighlight = A2(Highlight, + $Color.blue, + 0); + var Outline = F3(function (a, + b, + c) { + return {_: {} + ,color: a + ,radius: c + ,width: b}; + }); + var Dimensions = F4(function (a, + b, + c, + d) { + return {_: {} + ,bottom: d + ,left: a + ,right: b + ,top: c}; + }); + var uniformly = function (n) { + return A4(Dimensions, + n, + n, + n, + n); + }; + var noOutline = A3(Outline, + $Color.grey, + uniformly(0), + 0); + var defaultStyle = {_: {} + ,highlight: A2(Highlight, + $Color.blue, + 1) + ,outline: A3(Outline, + $Color.grey, + uniformly(1), + 2) + ,padding: uniformly(4) + ,style: $Text.defaultStyle}; + _elm.Graphics.Input.Field.values = {_op: _op + ,uniformly: uniformly + ,Dimensions: Dimensions + ,Outline: Outline + ,noOutline: noOutline + ,Highlight: Highlight + ,noHighlight: noHighlight + ,Style: Style + ,defaultStyle: defaultStyle + ,Content: Content + ,Selection: Selection + ,Forward: Forward + ,Backward: Backward + ,noContent: noContent + ,field: field + ,password: password + ,email: email}; + return _elm.Graphics.Input.Field.values; +}; +Elm.Html = Elm.Html || {}; +Elm.Html.make = function (_elm) { + "use strict"; + _elm.Html = _elm.Html || {}; + if (_elm.Html.values) + return _elm.Html.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Html", + $Graphics$Element = Elm.Graphics.Element.make(_elm), + $VirtualDom = Elm.VirtualDom.make(_elm); + var fromElement = $VirtualDom.fromElement; + var toElement = $VirtualDom.toElement; + var text = $VirtualDom.text; + var node = $VirtualDom.node; + var body = node("body"); + var section = node("section"); + var nav = node("nav"); + var article = node("article"); + var aside = node("aside"); + var h1 = node("h1"); + var h2 = node("h2"); + var h3 = node("h3"); + var h4 = node("h4"); + var h5 = node("h5"); + var h6 = node("h6"); + var header = node("header"); + var footer = node("footer"); + var address = node("address"); + var main$ = node("main"); + var p = node("p"); + var hr = node("hr"); + var pre = node("pre"); + var blockquote = node("blockquote"); + var ol = node("ol"); + var ul = node("ul"); + var li = node("li"); + var dl = node("dl"); + var dt = node("dt"); + var dd = node("dd"); + var figure = node("figure"); + var figcaption = node("figcaption"); + var div = node("div"); + var a = node("a"); + var em = node("em"); + var strong = node("strong"); + var small = node("small"); + var s = node("s"); + var cite = node("cite"); + var q = node("q"); + var dfn = node("dfn"); + var abbr = node("abbr"); + var time = node("time"); + var code = node("code"); + var $var = node("var"); + var samp = node("samp"); + var kbd = node("kbd"); + var sub = node("sub"); + var sup = node("sup"); + var i = node("i"); + var b = node("b"); + var u = node("u"); + var mark = node("mark"); + var ruby = node("ruby"); + var rt = node("rt"); + var rp = node("rp"); + var bdi = node("bdi"); + var bdo = node("bdo"); + var span = node("span"); + var br = node("br"); + var wbr = node("wbr"); + var ins = node("ins"); + var del = node("del"); + var img = node("img"); + var iframe = node("iframe"); + var embed = node("embed"); + var object = node("object"); + var param = node("param"); + var video = node("video"); + var audio = node("audio"); + var source = node("source"); + var track = node("track"); + var canvas = node("canvas"); + var svg = node("svg"); + var math = node("math"); + var table = node("table"); + var caption = node("caption"); + var colgroup = node("colgroup"); + var col = node("col"); + var tbody = node("tbody"); + var thead = node("thead"); + var tfoot = node("tfoot"); + var tr = node("tr"); + var td = node("td"); + var th = node("th"); + var form = node("form"); + var fieldset = node("fieldset"); + var legend = node("legend"); + var label = node("label"); + var input = node("input"); + var button = node("button"); + var select = node("select"); + var datalist = node("datalist"); + var optgroup = node("optgroup"); + var option = node("option"); + var textarea = node("textarea"); + var keygen = node("keygen"); + var output = node("output"); + var progress = node("progress"); + var meter = node("meter"); + var details = node("details"); + var summary = node("summary"); + var menuitem = node("menuitem"); + var menu = node("menu"); + _elm.Html.values = {_op: _op + ,node: node + ,text: text + ,toElement: toElement + ,fromElement: fromElement + ,body: body + ,section: section + ,nav: nav + ,article: article + ,aside: aside + ,h1: h1 + ,h2: h2 + ,h3: h3 + ,h4: h4 + ,h5: h5 + ,h6: h6 + ,header: header + ,footer: footer + ,address: address + ,main$: main$ + ,p: p + ,hr: hr + ,pre: pre + ,blockquote: blockquote + ,ol: ol + ,ul: ul + ,li: li + ,dl: dl + ,dt: dt + ,dd: dd + ,figure: figure + ,figcaption: figcaption + ,div: div + ,a: a + ,em: em + ,strong: strong + ,small: small + ,s: s + ,cite: cite + ,q: q + ,dfn: dfn + ,abbr: abbr + ,time: time + ,code: code + ,$var: $var + ,samp: samp + ,kbd: kbd + ,sub: sub + ,sup: sup + ,i: i + ,b: b + ,u: u + ,mark: mark + ,ruby: ruby + ,rt: rt + ,rp: rp + ,bdi: bdi + ,bdo: bdo + ,span: span + ,br: br + ,wbr: wbr + ,ins: ins + ,del: del + ,img: img + ,iframe: iframe + ,embed: embed + ,object: object + ,param: param + ,video: video + ,audio: audio + ,source: source + ,track: track + ,canvas: canvas + ,svg: svg + ,math: math + ,table: table + ,caption: caption + ,colgroup: colgroup + ,col: col + ,tbody: tbody + ,thead: thead + ,tfoot: tfoot + ,tr: tr + ,td: td + ,th: th + ,form: form + ,fieldset: fieldset + ,legend: legend + ,label: label + ,input: input + ,button: button + ,select: select + ,datalist: datalist + ,optgroup: optgroup + ,option: option + ,textarea: textarea + ,keygen: keygen + ,output: output + ,progress: progress + ,meter: meter + ,details: details + ,summary: summary + ,menuitem: menuitem + ,menu: menu}; + return _elm.Html.values; +}; +Elm.Json = Elm.Json || {}; +Elm.Json.Decode = Elm.Json.Decode || {}; +Elm.Json.Decode.make = function (_elm) { + "use strict"; + _elm.Json = _elm.Json || {}; + _elm.Json.Decode = _elm.Json.Decode || {}; + if (_elm.Json.Decode.values) + return _elm.Json.Decode.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Json.Decode", + $Array = Elm.Array.make(_elm), + $Dict = Elm.Dict.make(_elm), + $Json$Encode = Elm.Json.Encode.make(_elm), + $List = Elm.List.make(_elm), + $Maybe = Elm.Maybe.make(_elm), + $Native$Json = Elm.Native.Json.make(_elm), + $Result = Elm.Result.make(_elm); + var tuple8 = $Native$Json.decodeTuple8; + var tuple7 = $Native$Json.decodeTuple7; + var tuple6 = $Native$Json.decodeTuple6; + var tuple5 = $Native$Json.decodeTuple5; + var tuple4 = $Native$Json.decodeTuple4; + var tuple3 = $Native$Json.decodeTuple3; + var tuple2 = $Native$Json.decodeTuple2; + var tuple1 = $Native$Json.decodeTuple1; + var succeed = $Native$Json.succeed; + var fail = $Native$Json.fail; + var andThen = $Native$Json.andThen; + var customDecoder = $Native$Json.customDecoder; + var decodeValue = $Native$Json.runDecoderValue; + var value = $Native$Json.decodeValue; + var maybe = $Native$Json.decodeMaybe; + var $null = $Native$Json.decodeNull; + var array = $Native$Json.decodeArray; + var list = $Native$Json.decodeList; + var bool = $Native$Json.decodeBool; + var $int = $Native$Json.decodeInt; + var $float = $Native$Json.decodeFloat; + var string = $Native$Json.decodeString; + var oneOf = $Native$Json.oneOf; + var keyValuePairs = $Native$Json.decodeKeyValuePairs; + var object8 = $Native$Json.decodeObject8; + var object7 = $Native$Json.decodeObject7; + var object6 = $Native$Json.decodeObject6; + var object5 = $Native$Json.decodeObject5; + var object4 = $Native$Json.decodeObject4; + var object3 = $Native$Json.decodeObject3; + var object2 = $Native$Json.decodeObject2; + var object1 = $Native$Json.decodeObject1; + _op[":="] = $Native$Json.decodeField; + var at = F2(function (fields, + decoder) { + return A3($List.foldr, + F2(function (x,y) { + return A2(_op[":="],x,y); + }), + decoder, + fields); + }); + var decodeString = $Native$Json.runDecoderString; + var map = $Native$Json.decodeObject1; + var dict = function (decoder) { + return A2(map, + $Dict.fromList, + keyValuePairs(decoder)); + }; + var Decoder = {ctor: "Decoder"}; + _elm.Json.Decode.values = {_op: _op + ,Decoder: Decoder + ,map: map + ,decodeString: decodeString + ,at: at + ,object1: object1 + ,object2: object2 + ,object3: object3 + ,object4: object4 + ,object5: object5 + ,object6: object6 + ,object7: object7 + ,object8: object8 + ,keyValuePairs: keyValuePairs + ,dict: dict + ,oneOf: oneOf + ,string: string + ,$float: $float + ,$int: $int + ,bool: bool + ,list: list + ,array: array + ,$null: $null + ,maybe: maybe + ,value: value + ,decodeValue: decodeValue + ,customDecoder: customDecoder + ,andThen: andThen + ,fail: fail + ,succeed: succeed + ,tuple1: tuple1 + ,tuple2: tuple2 + ,tuple3: tuple3 + ,tuple4: tuple4 + ,tuple5: tuple5 + ,tuple6: tuple6 + ,tuple7: tuple7 + ,tuple8: tuple8}; + return _elm.Json.Decode.values; +}; +Elm.Json = Elm.Json || {}; +Elm.Json.Encode = Elm.Json.Encode || {}; +Elm.Json.Encode.make = function (_elm) { + "use strict"; + _elm.Json = _elm.Json || {}; + _elm.Json.Encode = _elm.Json.Encode || {}; + if (_elm.Json.Encode.values) + return _elm.Json.Encode.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Json.Encode", + $Array = Elm.Array.make(_elm), + $Native$Json = Elm.Native.Json.make(_elm); + var list = $Native$Json.encodeList; + var array = $Native$Json.encodeArray; + var object = $Native$Json.encodeObject; + var $null = $Native$Json.encodeNull; + var bool = $Native$Json.identity; + var $float = $Native$Json.identity; + var $int = $Native$Json.identity; + var string = $Native$Json.identity; + var encode = $Native$Json.encode; + var Value = {ctor: "Value"}; + _elm.Json.Encode.values = {_op: _op + ,Value: Value + ,encode: encode + ,string: string + ,$int: $int + ,$float: $float + ,bool: bool + ,$null: $null + ,object: object + ,array: array + ,list: list}; + return _elm.Json.Encode.values; +}; +Elm.List = Elm.List || {}; +Elm.List.make = function (_elm) { + "use strict"; + _elm.List = _elm.List || {}; + if (_elm.List.values) + return _elm.List.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "List", + $Basics = Elm.Basics.make(_elm), + $Maybe = Elm.Maybe.make(_elm), + $Native$List = Elm.Native.List.make(_elm); + var sortWith = $Native$List.sortWith; + var sortBy = $Native$List.sortBy; + var sort = function (xs) { + return A2(sortBy, + $Basics.identity, + xs); + }; + var repeat = $Native$List.repeat; + var drop = $Native$List.drop; + var take = $Native$List.take; + var map5 = $Native$List.map5; + var map4 = $Native$List.map4; + var map3 = $Native$List.map3; + var map2 = $Native$List.map2; + var any = $Native$List.any; + var all = F2(function (pred, + xs) { + return $Basics.not(A2(any, + function ($) { + return $Basics.not(pred($)); + }, + xs)); + }); + var foldr = $Native$List.foldr; + var foldl = $Native$List.foldl; + var length = function (xs) { + return A3(foldl, + F2(function (_v0,i) { + return function () { + return i + 1; + }(); + }), + 0, + xs); + }; + var sum = function (numbers) { + return A3(foldl, + F2(function (x,y) { + return x + y; + }), + 0, + numbers); + }; + var product = function (numbers) { + return A3(foldl, + F2(function (x,y) { + return x * y; + }), + 1, + numbers); + }; + var maximum = function (list) { + return function () { + switch (list.ctor) + {case "::": + return $Maybe.Just(A3(foldl, + $Basics.max, + list._0, + list._1));} + return $Maybe.Nothing; + }(); + }; + var minimum = function (list) { + return function () { + switch (list.ctor) + {case "::": + return $Maybe.Just(A3(foldl, + $Basics.min, + list._0, + list._1));} + return $Maybe.Nothing; + }(); + }; + var indexedMap = F2(function (f, + xs) { + return A3(map2, + f, + _L.range(0,length(xs) - 1), + xs); + }); + var member = F2(function (x, + xs) { + return A2(any, + function (a) { + return _U.eq(a,x); + }, + xs); + }); + var isEmpty = function (xs) { + return function () { + switch (xs.ctor) + {case "[]": return true;} + return false; + }(); + }; + var tail = function (list) { + return function () { + switch (list.ctor) + {case "::": + return $Maybe.Just(list._1); + case "[]": + return $Maybe.Nothing;} + _U.badCase($moduleName, + "between lines 87 and 95"); + }(); + }; + var head = function (list) { + return function () { + switch (list.ctor) + {case "::": + return $Maybe.Just(list._0); + case "[]": + return $Maybe.Nothing;} + _U.badCase($moduleName, + "between lines 75 and 84"); + }(); + }; + _op["::"] = $Native$List.cons; + var map = F2(function (f,xs) { + return A3(foldr, + F2(function (x,acc) { + return A2(_op["::"], + f(x), + acc); + }), + _L.fromArray([]), + xs); + }); + var filter = F2(function (pred, + xs) { + return function () { + var conditionalCons = F2(function (x, + xs$) { + return pred(x) ? A2(_op["::"], + x, + xs$) : xs$; + }); + return A3(foldr, + conditionalCons, + _L.fromArray([]), + xs); + }(); + }); + var maybeCons = F3(function (f, + mx, + xs) { + return function () { + var _v15 = f(mx); + switch (_v15.ctor) + {case "Just": + return A2(_op["::"],_v15._0,xs); + case "Nothing": return xs;} + _U.badCase($moduleName, + "between lines 179 and 186"); + }(); + }); + var filterMap = F2(function (f, + xs) { + return A3(foldr, + maybeCons(f), + _L.fromArray([]), + xs); + }); + var reverse = function (list) { + return A3(foldl, + F2(function (x,y) { + return A2(_op["::"],x,y); + }), + _L.fromArray([]), + list); + }; + var scanl = F3(function (f, + b, + xs) { + return function () { + var scan1 = F2(function (x, + accAcc) { + return function () { + switch (accAcc.ctor) + {case "::": return A2(_op["::"], + A2(f,x,accAcc._0), + accAcc); + case "[]": + return _L.fromArray([]);} + _U.badCase($moduleName, + "between lines 148 and 151"); + }(); + }); + return reverse(A3(foldl, + scan1, + _L.fromArray([b]), + xs)); + }(); + }); + var append = F2(function (xs, + ys) { + return function () { + switch (ys.ctor) + {case "[]": return xs;} + return A3(foldr, + F2(function (x,y) { + return A2(_op["::"],x,y); + }), + ys, + xs); + }(); + }); + var concat = function (lists) { + return A3(foldr, + append, + _L.fromArray([]), + lists); + }; + var concatMap = F2(function (f, + list) { + return concat(A2(map, + f, + list)); + }); + var partition = F2(function (pred, + list) { + return function () { + var step = F2(function (x, + _v21) { + return function () { + switch (_v21.ctor) + {case "_Tuple2": + return pred(x) ? {ctor: "_Tuple2" + ,_0: A2(_op["::"],x,_v21._0) + ,_1: _v21._1} : {ctor: "_Tuple2" + ,_0: _v21._0 + ,_1: A2(_op["::"], + x, + _v21._1)};} + _U.badCase($moduleName, + "between lines 301 and 303"); + }(); + }); + return A3(foldr, + step, + {ctor: "_Tuple2" + ,_0: _L.fromArray([]) + ,_1: _L.fromArray([])}, + list); + }(); + }); + var unzip = function (pairs) { + return function () { + var step = F2(function (_v25, + _v26) { + return function () { + switch (_v26.ctor) + {case "_Tuple2": + return function () { + switch (_v25.ctor) + {case "_Tuple2": + return {ctor: "_Tuple2" + ,_0: A2(_op["::"], + _v25._0, + _v26._0) + ,_1: A2(_op["::"], + _v25._1, + _v26._1)};} + _U.badCase($moduleName, + "on line 339, column 12 to 28"); + }();} + _U.badCase($moduleName, + "on line 339, column 12 to 28"); + }(); + }); + return A3(foldr, + step, + {ctor: "_Tuple2" + ,_0: _L.fromArray([]) + ,_1: _L.fromArray([])}, + pairs); + }(); + }; + var intersperse = F2(function (sep, + xs) { + return function () { + switch (xs.ctor) + {case "::": return function () { + var step = F2(function (x, + rest) { + return A2(_op["::"], + sep, + A2(_op["::"],x,rest)); + }); + var spersed = A3(foldr, + step, + _L.fromArray([]), + xs._1); + return A2(_op["::"], + xs._0, + spersed); + }(); + case "[]": + return _L.fromArray([]);} + _U.badCase($moduleName, + "between lines 350 and 361"); + }(); + }); + _elm.List.values = {_op: _op + ,isEmpty: isEmpty + ,length: length + ,reverse: reverse + ,member: member + ,head: head + ,tail: tail + ,filter: filter + ,take: take + ,drop: drop + ,repeat: repeat + ,append: append + ,concat: concat + ,intersperse: intersperse + ,partition: partition + ,unzip: unzip + ,map: map + ,map2: map2 + ,map3: map3 + ,map4: map4 + ,map5: map5 + ,filterMap: filterMap + ,concatMap: concatMap + ,indexedMap: indexedMap + ,foldr: foldr + ,foldl: foldl + ,sum: sum + ,product: product + ,maximum: maximum + ,minimum: minimum + ,all: all + ,any: any + ,scanl: scanl + ,sort: sort + ,sortBy: sortBy + ,sortWith: sortWith}; + return _elm.List.values; +}; +Elm.Markdown = Elm.Markdown || {}; +Elm.Markdown.make = function (_elm) { + "use strict"; + _elm.Markdown = _elm.Markdown || {}; + if (_elm.Markdown.values) + return _elm.Markdown.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Markdown", + $Graphics$Element = Elm.Graphics.Element.make(_elm), + $Html = Elm.Html.make(_elm), + $Maybe = Elm.Maybe.make(_elm), + $Native$Markdown = Elm.Native.Markdown.make(_elm); + var toElementWith = $Native$Markdown.toElementWith; + var toHtmlWith = $Native$Markdown.toHtmlWith; + var defaultOptions = {_: {} + ,githubFlavored: $Maybe.Just({_: {} + ,breaks: false + ,tables: false}) + ,sanitize: false + ,smartypants: false}; + var Options = F3(function (a, + b, + c) { + return {_: {} + ,githubFlavored: a + ,sanitize: b + ,smartypants: c}; + }); + var toElement = function (string) { + return A2($Native$Markdown.toElementWith, + defaultOptions, + string); + }; + var toHtml = function (string) { + return A2($Native$Markdown.toHtmlWith, + defaultOptions, + string); + }; + _elm.Markdown.values = {_op: _op + ,toHtml: toHtml + ,toElement: toElement + ,Options: Options + ,defaultOptions: defaultOptions + ,toHtmlWith: toHtmlWith + ,toElementWith: toElementWith}; + return _elm.Markdown.values; +}; +Elm.Maybe = Elm.Maybe || {}; +Elm.Maybe.make = function (_elm) { + "use strict"; + _elm.Maybe = _elm.Maybe || {}; + if (_elm.Maybe.values) + return _elm.Maybe.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Maybe"; + var withDefault = F2(function ($default, + maybe) { + return function () { + switch (maybe.ctor) + {case "Just": return maybe._0; + case "Nothing": + return $default;} + _U.badCase($moduleName, + "between lines 45 and 56"); + }(); + }); + var Nothing = {ctor: "Nothing"}; + var oneOf = function (maybes) { + return function () { + switch (maybes.ctor) + {case "::": return function () { + switch (maybes._0.ctor) + {case "Just": return maybes._0; + case "Nothing": + return oneOf(maybes._1);} + _U.badCase($moduleName, + "between lines 64 and 73"); + }(); + case "[]": return Nothing;} + _U.badCase($moduleName, + "between lines 59 and 73"); + }(); + }; + var andThen = F2(function (maybeValue, + callback) { + return function () { + switch (maybeValue.ctor) + {case "Just": + return callback(maybeValue._0); + case "Nothing": return Nothing;} + _U.badCase($moduleName, + "between lines 110 and 112"); + }(); + }); + var Just = function (a) { + return {ctor: "Just",_0: a}; + }; + var map = F2(function (f, + maybe) { + return function () { + switch (maybe.ctor) + {case "Just": + return Just(f(maybe._0)); + case "Nothing": return Nothing;} + _U.badCase($moduleName, + "between lines 76 and 107"); + }(); + }); + _elm.Maybe.values = {_op: _op + ,andThen: andThen + ,map: map + ,withDefault: withDefault + ,oneOf: oneOf + ,Just: Just + ,Nothing: Nothing}; + return _elm.Maybe.values; +}; +Elm.Native.Array = {}; +Elm.Native.Array.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Array = localRuntime.Native.Array || {}; + if (localRuntime.Native.Array.values) + { + return localRuntime.Native.Array.values; + } + if ('values' in Elm.Native.Array) + { + return localRuntime.Native.Array.values = Elm.Native.Array.values; + } + + var List = Elm.Native.List.make(localRuntime); + + // A RRB-Tree has two distinct data types. + // Leaf -> "height" is always 0 + // "table" is an array of elements + // Node -> "height" is always greater than 0 + // "table" is an array of child nodes + // "lengths" is an array of accumulated lengths of the child nodes + + // M is the maximal table size. 32 seems fast. E is the allowed increase + // of search steps when concatting to find an index. Lower values will + // decrease balancing, but will increase search steps. + var M = 32; + var E = 2; + + // An empty array. + var empty = { + ctor: "_Array", + height: 0, + table: new Array() + }; + + + function get(i, array) + { + if (i < 0 || i >= length(array)) + { + throw new Error( + "Index " + i + " is out of range. Check the length of " + + "your array first or use getMaybe or getWithDefault."); + } + return unsafeGet(i, array); + } + + + function unsafeGet(i, array) + { + for (var x = array.height; x > 0; x--) + { + var slot = i >> (x * 5); + while (array.lengths[slot] <= i) + { + slot++; + } + if (slot > 0) + { + i -= array.lengths[slot - 1]; + } + array = array.table[slot]; + } + return array.table[i]; + } + + + // Sets the value at the index i. Only the nodes leading to i will get + // copied and updated. + function set(i, item, array) + { + if (i < 0 || length(array) <= i) + { + return array; + } + return unsafeSet(i, item, array); + } + + + function unsafeSet(i, item, array) + { + array = nodeCopy(array); + + if (array.height == 0) + { + array.table[i] = item; + } + else + { + var slot = getSlot(i, array); + if (slot > 0) + { + i -= array.lengths[slot - 1]; + } + array.table[slot] = unsafeSet(i, item, array.table[slot]); + } + return array; + } + + + function initialize(len, f) + { + if (len == 0) + { + return empty; + } + var h = Math.floor( Math.log(len) / Math.log(M) ); + return initialize_(f, h, 0, len); + } + + function initialize_(f, h, from, to) + { + if (h == 0) + { + var table = new Array((to - from) % (M + 1)); + for (var i = 0; i < table.length; i++) + { + table[i] = f(from + i); + } + return { + ctor: "_Array", + height: 0, + table: table + }; + } + + var step = Math.pow(M, h); + var table = new Array(Math.ceil((to - from) / step)); + var lengths = new Array(table.length); + for (var i = 0; i < table.length; i++) + { + table[i] = initialize_(f, h - 1, from + (i * step), Math.min(from + ((i + 1) * step), to)); + lengths[i] = length(table[i]) + (i > 0 ? lengths[i-1] : 0); + } + return { + ctor: "_Array", + height: h, + table: table, + lengths: lengths + }; + } + + function fromList(list) + { + if (list == List.Nil) + { + return empty; + } + + // Allocate M sized blocks (table) and write list elements to it. + var table = new Array(M); + var nodes = new Array(); + var i = 0; + + while (list.ctor !== '[]') + { + table[i] = list._0; + list = list._1; + i++; + + // table is full, so we can push a leaf containing it into the + // next node. + if (i == M) + { + var leaf = { + ctor: "_Array", + height: 0, + table: table + }; + fromListPush(leaf, nodes); + table = new Array(M); + i = 0; + } + } + + // Maybe there is something left on the table. + if (i > 0) + { + var leaf = { + ctor: "_Array", + height: 0, + table: table.splice(0,i) + }; + fromListPush(leaf, nodes); + } + + // Go through all of the nodes and eventually push them into higher nodes. + for (var h = 0; h < nodes.length - 1; h++) + { + if (nodes[h].table.length > 0) + { + fromListPush(nodes[h], nodes); + } + } + + var head = nodes[nodes.length - 1]; + if (head.height > 0 && head.table.length == 1) + { + return head.table[0]; + } + else + { + return head; + } + } + + // Push a node into a higher node as a child. + function fromListPush(toPush, nodes) + { + var h = toPush.height; + + // Maybe the node on this height does not exist. + if (nodes.length == h) + { + var node = { + ctor: "_Array", + height: h + 1, + table: new Array(), + lengths: new Array() + }; + nodes.push(node); + } + + nodes[h].table.push(toPush); + var len = length(toPush); + if (nodes[h].lengths.length > 0) + { + len += nodes[h].lengths[nodes[h].lengths.length - 1]; + } + nodes[h].lengths.push(len); + + if (nodes[h].table.length == M) + { + fromListPush(nodes[h], nodes); + nodes[h] = { + ctor: "_Array", + height: h + 1, + table: new Array(), + lengths: new Array() + }; + } + } + + // Pushes an item via push_ to the bottom right of a tree. + function push(item, a) + { + var pushed = push_(item, a); + if (pushed !== null) + { + return pushed; + } + + var newTree = create(item, a.height); + return siblise(a, newTree); + } + + // Recursively tries to push an item to the bottom-right most + // tree possible. If there is no space left for the item, + // null will be returned. + function push_(item, a) + { + // Handle resursion stop at leaf level. + if (a.height == 0) + { + if (a.table.length < M) + { + var newA = { + ctor: "_Array", + height: 0, + table: a.table.slice() + }; + newA.table.push(item); + return newA; + } + else + { + return null; + } + } + + // Recursively push + var pushed = push_(item, botRight(a)); + + // There was space in the bottom right tree, so the slot will + // be updated. + if (pushed != null) + { + var newA = nodeCopy(a); + newA.table[newA.table.length - 1] = pushed; + newA.lengths[newA.lengths.length - 1]++; + return newA; + } + + // When there was no space left, check if there is space left + // for a new slot with a tree which contains only the item + // at the bottom. + if (a.table.length < M) + { + var newSlot = create(item, a.height - 1); + var newA = nodeCopy(a); + newA.table.push(newSlot); + newA.lengths.push(newA.lengths[newA.lengths.length - 1] + length(newSlot)); + return newA; + } + else + { + return null; + } + } + + // Converts an array into a list of elements. + function toList(a) + { + return toList_(List.Nil, a); + } + + function toList_(list, a) + { + for (var i = a.table.length - 1; i >= 0; i--) + { + list = + a.height == 0 + ? List.Cons(a.table[i], list) + : toList_(list, a.table[i]); + } + return list; + } + + // Maps a function over the elements of an array. + function map(f, a) + { + var newA = { + ctor: "_Array", + height: a.height, + table: new Array(a.table.length) + }; + if (a.height > 0) + { + newA.lengths = a.lengths; + } + for (var i = 0; i < a.table.length; i++) + { + newA.table[i] = + a.height == 0 + ? f(a.table[i]) + : map(f, a.table[i]); + } + return newA; + } + + // Maps a function over the elements with their index as first argument. + function indexedMap(f, a) + { + return indexedMap_(f, a, 0); + } + + function indexedMap_(f, a, from) + { + var newA = { + ctor: "_Array", + height: a.height, + table: new Array(a.table.length) + }; + if (a.height > 0) + { + newA.lengths = a.lengths; + } + for (var i = 0; i < a.table.length; i++) + { + newA.table[i] = + a.height == 0 + ? A2(f, from + i, a.table[i]) + : indexedMap_(f, a.table[i], i == 0 ? 0 : a.lengths[i - 1]); + } + return newA; + } + + function foldl(f, b, a) + { + if (a.height == 0) + { + for (var i = 0; i < a.table.length; i++) + { + b = A2(f, a.table[i], b); + } + } + else + { + for (var i = 0; i < a.table.length; i++) + { + b = foldl(f, b, a.table[i]); + } + } + return b; + } + + function foldr(f, b, a) + { + if (a.height == 0) + { + for (var i = a.table.length; i--; ) + { + b = A2(f, a.table[i], b); + } + } + else + { + for (var i = a.table.length; i--; ) + { + b = foldr(f, b, a.table[i]); + } + } + return b; + } + + // TODO: currently, it slices the right, then the left. This can be + // optimized. + function slice(from, to, a) + { + if (from < 0) + { + from += length(a); + } + if (to < 0) + { + to += length(a); + } + return sliceLeft(from, sliceRight(to, a)); + } + + function sliceRight(to, a) + { + if (to == length(a)) + { + return a; + } + + // Handle leaf level. + if (a.height == 0) + { + var newA = { ctor:"_Array", height:0 }; + newA.table = a.table.slice(0, to); + return newA; + } + + // Slice the right recursively. + var right = getSlot(to, a); + var sliced = sliceRight(to - (right > 0 ? a.lengths[right - 1] : 0), a.table[right]); + + // Maybe the a node is not even needed, as sliced contains the whole slice. + if (right == 0) + { + return sliced; + } + + // Create new node. + var newA = { + ctor: "_Array", + height: a.height, + table: a.table.slice(0, right), + lengths: a.lengths.slice(0, right) + }; + if (sliced.table.length > 0) + { + newA.table[right] = sliced; + newA.lengths[right] = length(sliced) + (right > 0 ? newA.lengths[right - 1] : 0); + } + return newA; + } + + function sliceLeft(from, a) + { + if (from == 0) + { + return a; + } + + // Handle leaf level. + if (a.height == 0) + { + var newA = { ctor:"_Array", height:0 }; + newA.table = a.table.slice(from, a.table.length + 1); + return newA; + } + + // Slice the left recursively. + var left = getSlot(from, a); + var sliced = sliceLeft(from - (left > 0 ? a.lengths[left - 1] : 0), a.table[left]); + + // Maybe the a node is not even needed, as sliced contains the whole slice. + if (left == a.table.length - 1) + { + return sliced; + } + + // Create new node. + var newA = { + ctor: "_Array", + height: a.height, + table: a.table.slice(left, a.table.length + 1), + lengths: new Array(a.table.length - left) + }; + newA.table[0] = sliced; + var len = 0; + for (var i = 0; i < newA.table.length; i++) + { + len += length(newA.table[i]); + newA.lengths[i] = len; + } + + return newA; + } + + // Appends two trees. + function append(a,b) + { + if (a.table.length === 0) + { + return b; + } + if (b.table.length === 0) + { + return a; + } + + var c = append_(a, b); + + // Check if both nodes can be crunshed together. + if (c[0].table.length + c[1].table.length <= M) + { + if (c[0].table.length === 0) + { + return c[1]; + } + if (c[1].table.length === 0) + { + return c[0]; + } + + // Adjust .table and .lengths + c[0].table = c[0].table.concat(c[1].table); + if (c[0].height > 0) + { + var len = length(c[0]); + for (var i = 0; i < c[1].lengths.length; i++) + { + c[1].lengths[i] += len; + } + c[0].lengths = c[0].lengths.concat(c[1].lengths); + } + + return c[0]; + } + + if (c[0].height > 0) + { + var toRemove = calcToRemove(a, b); + if (toRemove > E) + { + c = shuffle(c[0], c[1], toRemove); + } + } + + return siblise(c[0], c[1]); + } + + // Returns an array of two nodes; right and left. One node _may_ be empty. + function append_(a, b) + { + if (a.height === 0 && b.height === 0) + { + return [a, b]; + } + + if (a.height !== 1 || b.height !== 1) + { + if (a.height === b.height) + { + a = nodeCopy(a); + b = nodeCopy(b); + var appended = append_(botRight(a), botLeft(b)); + + insertRight(a, appended[1]); + insertLeft(b, appended[0]); + } + else if (a.height > b.height) + { + a = nodeCopy(a); + var appended = append_(botRight(a), b); + + insertRight(a, appended[0]); + b = parentise(appended[1], appended[1].height + 1); + } + else + { + b = nodeCopy(b); + var appended = append_(a, botLeft(b)); + + var left = appended[0].table.length === 0 ? 0 : 1; + var right = left === 0 ? 1 : 0; + insertLeft(b, appended[left]); + a = parentise(appended[right], appended[right].height + 1); + } + } + + // Check if balancing is needed and return based on that. + if (a.table.length === 0 || b.table.length === 0) + { + return [a,b]; + } + + var toRemove = calcToRemove(a, b); + if (toRemove <= E) + { + return [a,b]; + } + return shuffle(a, b, toRemove); + } + + // Helperfunctions for append_. Replaces a child node at the side of the parent. + function insertRight(parent, node) + { + var index = parent.table.length - 1; + parent.table[index] = node; + parent.lengths[index] = length(node) + parent.lengths[index] += index > 0 ? parent.lengths[index - 1] : 0; + } + + function insertLeft(parent, node) + { + if (node.table.length > 0) + { + parent.table[0] = node; + parent.lengths[0] = length(node); + + var len = length(parent.table[0]); + for (var i = 1; i < parent.lengths.length; i++) + { + len += length(parent.table[i]); + parent.lengths[i] = len; + } + } + else + { + parent.table.shift(); + for (var i = 1; i < parent.lengths.length; i++) + { + parent.lengths[i] = parent.lengths[i] - parent.lengths[0]; + } + parent.lengths.shift(); + } + } + + // Returns the extra search steps for E. Refer to the paper. + function calcToRemove(a, b) + { + var subLengths = 0; + for (var i = 0; i < a.table.length; i++) + { + subLengths += a.table[i].table.length; + } + for (var i = 0; i < b.table.length; i++) + { + subLengths += b.table[i].table.length; + } + + var toRemove = a.table.length + b.table.length + return toRemove - (Math.floor((subLengths - 1) / M) + 1); + } + + // get2, set2 and saveSlot are helpers for accessing elements over two arrays. + function get2(a, b, index) + { + return index < a.length + ? a[index] + : b[index - a.length]; + } + + function set2(a, b, index, value) + { + if (index < a.length) + { + a[index] = value; + } + else + { + b[index - a.length] = value; + } + } + + function saveSlot(a, b, index, slot) + { + set2(a.table, b.table, index, slot); + + var l = (index == 0 || index == a.lengths.length) + ? 0 + : get2(a.lengths, a.lengths, index - 1); + + set2(a.lengths, b.lengths, index, l + length(slot)); + } + + // Creates a node or leaf with a given length at their arrays for perfomance. + // Is only used by shuffle. + function createNode(h, length) + { + if (length < 0) + { + length = 0; + } + var a = { + ctor: "_Array", + height: h, + table: new Array(length) + }; + if (h > 0) + { + a.lengths = new Array(length); + } + return a; + } + + // Returns an array of two balanced nodes. + function shuffle(a, b, toRemove) + { + var newA = createNode(a.height, Math.min(M, a.table.length + b.table.length - toRemove)); + var newB = createNode(a.height, newA.table.length - (a.table.length + b.table.length - toRemove)); + + // Skip the slots with size M. More precise: copy the slot references + // to the new node + var read = 0; + while (get2(a.table, b.table, read).table.length % M == 0) + { + set2(newA.table, newB.table, read, get2(a.table, b.table, read)); + set2(newA.lengths, newB.lengths, read, get2(a.lengths, b.lengths, read)); + read++; + } + + // Pulling items from left to right, caching in a slot before writing + // it into the new nodes. + var write = read; + var slot = new createNode(a.height - 1, 0); + var from = 0; + + // If the current slot is still containing data, then there will be at + // least one more write, so we do not break this loop yet. + while (read - write - (slot.table.length > 0 ? 1 : 0) < toRemove) + { + // Find out the max possible items for copying. + var source = get2(a.table, b.table, read); + var to = Math.min(M - slot.table.length, source.table.length) + + // Copy and adjust size table. + slot.table = slot.table.concat(source.table.slice(from, to)); + if (slot.height > 0) + { + var len = slot.lengths.length; + for (var i = len; i < len + to - from; i++) + { + slot.lengths[i] = length(slot.table[i]); + slot.lengths[i] += (i > 0 ? slot.lengths[i - 1] : 0); + } + } + + from += to; + + // Only proceed to next slots[i] if the current one was + // fully copied. + if (source.table.length <= to) + { + read++; from = 0; + } + + // Only create a new slot if the current one is filled up. + if (slot.table.length == M) + { + saveSlot(newA, newB, write, slot); + slot = createNode(a.height - 1,0); + write++; + } + } + + // Cleanup after the loop. Copy the last slot into the new nodes. + if (slot.table.length > 0) + { + saveSlot(newA, newB, write, slot); + write++; + } + + // Shift the untouched slots to the left + while (read < a.table.length + b.table.length ) + { + saveSlot(newA, newB, write, get2(a.table, b.table, read)); + read++; + write++; + } + + return [newA, newB]; + } + + // Navigation functions + function botRight(a) + { + return a.table[a.table.length - 1]; + } + function botLeft(a) + { + return a.table[0]; + } + + // Copies a node for updating. Note that you should not use this if + // only updating only one of "table" or "lengths" for performance reasons. + function nodeCopy(a) + { + var newA = { + ctor: "_Array", + height: a.height, + table: a.table.slice() + }; + if (a.height > 0) + { + newA.lengths = a.lengths.slice(); + } + return newA; + } + + // Returns how many items are in the tree. + function length(array) + { + if (array.height == 0) + { + return array.table.length; + } + else + { + return array.lengths[array.lengths.length - 1]; + } + } + + // Calculates in which slot of "table" the item probably is, then + // find the exact slot via forward searching in "lengths". Returns the index. + function getSlot(i, a) + { + var slot = i >> (5 * a.height); + while (a.lengths[slot] <= i) + { + slot++; + } + return slot; + } + + // Recursively creates a tree with a given height containing + // only the given item. + function create(item, h) + { + if (h == 0) + { + return { + ctor: "_Array", + height: 0, + table: [item] + }; + } + return { + ctor: "_Array", + height: h, + table: [create(item, h - 1)], + lengths: [1] + }; + } + + // Recursively creates a tree that contains the given tree. + function parentise(tree, h) + { + if (h == tree.height) + { + return tree; + } + + return { + ctor: "_Array", + height: h, + table: [parentise(tree, h - 1)], + lengths: [length(tree)] + }; + } + + // Emphasizes blood brotherhood beneath two trees. + function siblise(a, b) + { + return { + ctor: "_Array", + height: a.height + 1, + table: [a, b], + lengths: [length(a), length(a) + length(b)] + }; + } + + function toJSArray(a) + { + var jsArray = new Array(length(a)); + toJSArray_(jsArray, 0, a); + return jsArray; + } + + function toJSArray_(jsArray, i, a) + { + for (var t = 0; t < a.table.length; t++) + { + if (a.height == 0) + { + jsArray[i + t] = a.table[t]; + } + else + { + var inc = t == 0 ? 0 : a.lengths[t - 1]; + toJSArray_(jsArray, i + inc, a.table[t]); + } + } + } + + function fromJSArray(jsArray) + { + if (jsArray.length == 0) + { + return empty; + } + var h = Math.floor(Math.log(jsArray.length) / Math.log(M)); + return fromJSArray_(jsArray, h, 0, jsArray.length); + } + + function fromJSArray_(jsArray, h, from, to) + { + if (h == 0) + { + return { + ctor: "_Array", + height: 0, + table: jsArray.slice(from, to) + }; + } + + var step = Math.pow(M, h); + var table = new Array(Math.ceil((to - from) / step)); + var lengths = new Array(table.length); + for (var i = 0; i < table.length; i++) + { + table[i] = fromJSArray_(jsArray, h - 1, from + (i * step), Math.min(from + ((i + 1) * step), to)); + lengths[i] = length(table[i]) + (i > 0 ? lengths[i-1] : 0); + } + return { + ctor: "_Array", + height: h, + table: table, + lengths: lengths + }; + } + + Elm.Native.Array.values = { + empty: empty, + fromList: fromList, + toList: toList, + initialize: F2(initialize), + append: F2(append), + push: F2(push), + slice: F3(slice), + get: F2(get), + set: F3(set), + map: F2(map), + indexedMap: F2(indexedMap), + foldl: F3(foldl), + foldr: F3(foldr), + length: length, + + toJSArray:toJSArray, + fromJSArray:fromJSArray + }; + + return localRuntime.Native.Array.values = Elm.Native.Array.values; + +} + +Elm.Native.Basics = {}; +Elm.Native.Basics.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Basics = localRuntime.Native.Basics || {}; + if (localRuntime.Native.Basics.values) + { + return localRuntime.Native.Basics.values; + } + + var Utils = Elm.Native.Utils.make(localRuntime); + + function div(a, b) + { + return (a/b)|0; + } + function rem(a, b) + { + return a % b; + } + function mod(a, b) + { + if (b === 0) + { + throw new Error("Cannot perform mod 0. Division by zero error."); + } + var r = a % b; + var m = a === 0 ? 0 : (b > 0 ? (a >= 0 ? r : r+b) : -mod(-a,-b)); + + return m === b ? 0 : m; + } + function logBase(base, n) + { + return Math.log(n) / Math.log(base); + } + function negate(n) + { + return -n; + } + function abs(n) + { + return n < 0 ? -n : n; + } + + function min(a, b) + { + return Utils.cmp(a,b) < 0 ? a : b; + } + function max(a, b) + { + return Utils.cmp(a,b) > 0 ? a : b; + } + function clamp(lo, hi, n) + { + return Utils.cmp(n,lo) < 0 ? lo : Utils.cmp(n,hi) > 0 ? hi : n; + } + + function xor(a, b) + { + return a !== b; + } + function not(b) + { + return !b; + } + function isInfinite(n) + { + return n === Infinity || n === -Infinity + } + + function truncate(n) + { + return n|0; + } + + function degrees(d) + { + return d * Math.PI / 180; + } + function turns(t) + { + return 2 * Math.PI * t; + } + function fromPolar(point) + { + var r = point._0; + var t = point._1; + return Utils.Tuple2(r * Math.cos(t), r * Math.sin(t)); + } + function toPolar(point) + { + var x = point._0; + var y = point._1; + return Utils.Tuple2(Math.sqrt(x * x + y * y), Math.atan2(y,x)); + } + + return localRuntime.Native.Basics.values = { + div: F2(div), + rem: F2(rem), + mod: F2(mod), + + pi: Math.PI, + e: Math.E, + cos: Math.cos, + sin: Math.sin, + tan: Math.tan, + acos: Math.acos, + asin: Math.asin, + atan: Math.atan, + atan2: F2(Math.atan2), + + degrees: degrees, + turns: turns, + fromPolar: fromPolar, + toPolar: toPolar, + + sqrt: Math.sqrt, + logBase: F2(logBase), + negate: negate, + abs: abs, + min: F2(min), + max: F2(max), + clamp: F3(clamp), + compare: Utils.compare, + + xor: F2(xor), + not: not, + + truncate: truncate, + ceiling: Math.ceil, + floor: Math.floor, + round: Math.round, + toFloat: function(x) { return x; }, + isNaN: isNaN, + isInfinite: isInfinite + }; +}; + +Elm.Native.Char = {}; +Elm.Native.Char.make = function(localRuntime) { + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Char = localRuntime.Native.Char || {}; + if (localRuntime.Native.Char.values) + { + return localRuntime.Native.Char.values; + } + + var Utils = Elm.Native.Utils.make(localRuntime); + + return localRuntime.Native.Char.values = { + fromCode : function(c) { return Utils.chr(String.fromCharCode(c)); }, + toCode : function(c) { return c.charCodeAt(0); }, + toUpper : function(c) { return Utils.chr(c.toUpperCase()); }, + toLower : function(c) { return Utils.chr(c.toLowerCase()); }, + toLocaleUpper : function(c) { return Utils.chr(c.toLocaleUpperCase()); }, + toLocaleLower : function(c) { return Utils.chr(c.toLocaleLowerCase()); }, + }; +}; + +Elm.Native.Color = {}; +Elm.Native.Color.make = function(localRuntime) { + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Color = localRuntime.Native.Color || {}; + if (localRuntime.Native.Color.values) + { + return localRuntime.Native.Color.values; + } + + function toCss(c) + { + var format = ''; + var colors = ''; + if (c.ctor === 'RGBA') + { + format = 'rgb'; + colors = c._0 + ', ' + c._1 + ', ' + c._2; + } + else + { + format = 'hsl'; + colors = (c._0 * 180 / Math.PI) + ', ' + + (c._1 * 100) + '%, ' + + (c._2 * 100) + '%'; + } + if (c._3 === 1) + { + return format + '(' + colors + ')'; + } + else + { + return format + 'a(' + colors + ', ' + c._3 + ')'; + } + } + + return localRuntime.Native.Color.values = { + toCss: toCss + }; + +}; + +Elm.Native.Debug = {}; +Elm.Native.Debug.make = function(localRuntime) { + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Debug = localRuntime.Native.Debug || {}; + if (localRuntime.Native.Debug.values) + { + return localRuntime.Native.Debug.values; + } + + var toString = Elm.Native.Show.make(localRuntime).toString; + + function log(tag, value) + { + var msg = tag + ': ' + toString(value); + var process = process || {}; + if (process.stdout) + { + process.stdout.write(msg); + } + else + { + console.log(msg); + } + return value; + } + + function crash(message) + { + throw new Error(message); + } + + function tracePath(tag, form) + { + if (localRuntime.debug) + { + return localRuntime.debug.trace(tag, form); + } + return form; + } + + function watch(tag, value) + { + if (localRuntime.debug) + { + localRuntime.debug.watch(tag, value); + } + return value; + } + + function watchSummary(tag, summarize, value) + { + if (localRuntime.debug) + { + localRuntime.debug.watch(tag, summarize(value)); + } + return value; + } + + return localRuntime.Native.Debug.values = { + crash: crash, + tracePath: F2(tracePath), + log: F2(log), + watch: F2(watch), + watchSummary:F3(watchSummary), + }; +}; + + +// setup +Elm.Native = Elm.Native || {}; +Elm.Native.Graphics = Elm.Native.Graphics || {}; +Elm.Native.Graphics.Collage = Elm.Native.Graphics.Collage || {}; + +// definition +Elm.Native.Graphics.Collage.make = function(localRuntime) { + 'use strict'; + + // attempt to short-circuit + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Graphics = localRuntime.Native.Graphics || {}; + localRuntime.Native.Graphics.Collage = localRuntime.Native.Graphics.Collage || {}; + if ('values' in localRuntime.Native.Graphics.Collage) + { + return localRuntime.Native.Graphics.Collage.values; + } + + // okay, we cannot short-ciruit, so now we define everything + var Color = Elm.Native.Color.make(localRuntime); + var List = Elm.Native.List.make(localRuntime); + var NativeElement = Elm.Native.Graphics.Element.make(localRuntime); + var Transform = Elm.Transform2D.make(localRuntime); + var Utils = Elm.Native.Utils.make(localRuntime); + + function setStrokeStyle(ctx, style) + { + ctx.lineWidth = style.width; + + var cap = style.cap.ctor; + ctx.lineCap = cap === 'Flat' + ? 'butt' + : cap === 'Round' + ? 'round' + : 'square'; + + var join = style.join.ctor; + ctx.lineJoin = join === 'Smooth' + ? 'round' + : join === 'Sharp' + ? 'miter' + : 'bevel'; + + ctx.miterLimit = style.join._0 || 10; + ctx.strokeStyle = Color.toCss(style.color); + } + + function setFillStyle(ctx, style) + { + var sty = style.ctor; + ctx.fillStyle = sty === 'Solid' + ? Color.toCss(style._0) + : sty === 'Texture' + ? texture(redo, ctx, style._0) + : gradient(ctx, style._0); + } + + function trace(ctx, path) + { + var points = List.toArray(path); + var i = points.length - 1; + if (i <= 0) + { + return; + } + ctx.moveTo(points[i]._0, points[i]._1); + while (i--) + { + ctx.lineTo(points[i]._0, points[i]._1); + } + if (path.closed) + { + i = points.length - 1; + ctx.lineTo(points[i]._0, points[i]._1); + } + } + + function line(ctx,style,path) + { + (style.dashing.ctor === '[]') + ? trace(ctx, path) + : customLineHelp(ctx, style, path); + ctx.scale(1,-1); + ctx.stroke(); + } + + function customLineHelp(ctx, style, path) + { + var points = List.toArray(path); + if (path.closed) + { + points.push(points[0]); + } + var pattern = List.toArray(style.dashing); + var i = points.length - 1; + if (i <= 0) + { + return; + } + var x0 = points[i]._0, y0 = points[i]._1; + var x1=0, y1=0, dx=0, dy=0, remaining=0, nx=0, ny=0; + var pindex = 0, plen = pattern.length; + var draw = true, segmentLength = pattern[0]; + ctx.moveTo(x0,y0); + while (i--) + { + x1 = points[i]._0; + y1 = points[i]._1; + dx = x1 - x0; + dy = y1 - y0; + remaining = Math.sqrt(dx * dx + dy * dy); + while (segmentLength <= remaining) + { + x0 += dx * segmentLength / remaining; + y0 += dy * segmentLength / remaining; + ctx[draw ? 'lineTo' : 'moveTo'](x0, y0); + // update starting position + dx = x1 - x0; + dy = y1 - y0; + remaining = Math.sqrt(dx * dx + dy * dy); + // update pattern + draw = !draw; + pindex = (pindex + 1) % plen; + segmentLength = pattern[pindex]; + } + if (remaining > 0) + { + ctx[draw ? 'lineTo' : 'moveTo'](x1, y1); + segmentLength -= remaining; + } + x0 = x1; + y0 = y1; + } + } + + function drawLine(ctx, style, path) + { + setStrokeStyle(ctx, style); + return line(ctx, style, path); + } + + function texture(redo, ctx, src) + { + var img = new Image(); + img.src = src; + img.onload = redo; + return ctx.createPattern(img, 'repeat'); + } + + function gradient(ctx, grad) + { + var g; + var stops = []; + if (grad.ctor === 'Linear') + { + var p0 = grad._0, p1 = grad._1; + g = ctx.createLinearGradient(p0._0, -p0._1, p1._0, -p1._1); + stops = List.toArray(grad._2); + } + else + { + var p0 = grad._0, p2 = grad._2; + g = ctx.createRadialGradient(p0._0, -p0._1, grad._1, p2._0, -p2._1, grad._3); + stops = List.toArray(grad._4); + } + var len = stops.length; + for (var i = 0; i < len; ++i) + { + var stop = stops[i]; + g.addColorStop(stop._0, Color.toCss(stop._1)); + } + return g; + } + + function drawShape(redo, ctx, style, path) + { + trace(ctx, path); + setFillStyle(ctx, style); + ctx.scale(1,-1); + ctx.fill(); + } + + + // TEXT RENDERING + + function fillText(redo, ctx, text) + { + drawText(ctx, text, ctx.fillText); + } + + function strokeText(redo, ctx, style, text) + { + setStrokeStyle(ctx, style); + // Use native canvas API for dashes only for text for now + // Degrades to non-dashed on IE 9 + 10 + if (style.dashing.ctor !== '[]' && ctx.setLineDash) + { + var pattern = List.toArray(style.dashing); + ctx.setLineDash(pattern); + } + drawText(ctx, text, ctx.strokeText); + } + + function drawText(ctx, text, canvasDrawFn) + { + var textChunks = chunkText(defaultContext, text); + + var totalWidth = 0; + var maxHeight = 0; + var numChunks = textChunks.length; + + ctx.scale(1,-1); + + for (var i = numChunks; i--; ) + { + var chunk = textChunks[i]; + ctx.font = chunk.font; + var metrics = ctx.measureText(chunk.text); + chunk.width = metrics.width; + totalWidth += chunk.width; + if (chunk.height > maxHeight) + { + maxHeight = chunk.height; + } + } + + var x = -totalWidth / 2.0; + for (var i = 0; i < numChunks; ++i) + { + var chunk = textChunks[i]; + ctx.font = chunk.font; + ctx.fillStyle = chunk.color; + canvasDrawFn.call(ctx, chunk.text, x, maxHeight / 2); + x += chunk.width; + } + } + + function toFont(props) + { + return [ + props['font-style'], + props['font-variant'], + props['font-weight'], + props['font-size'], + props['font-family'] + ].join(' '); + } + + + // Convert the object returned by the text module + // into something we can use for styling canvas text + function chunkText(context, text) + { + var tag = text.ctor; + if (tag === 'Text:Append') + { + var leftChunks = chunkText(context, text._0); + var rightChunks = chunkText(context, text._1); + return leftChunks.concat(rightChunks); + } + if (tag === 'Text:Text') + { + return [{ + text: text._0, + color: context.color, + height: context['font-size'].slice(0,-2) | 0, + font: toFont(context) + }]; + } + if (tag === 'Text:Meta') + { + var newContext = freshContext(text._0, context); + return chunkText(newContext, text._1); + } + } + + function freshContext(props, ctx) + { + return { + 'font-style': props['font-style'] || ctx['font-style'], + 'font-variant': props['font-variant'] || ctx['font-variant'], + 'font-weight': props['font-weight'] || ctx['font-weight'], + 'font-size': props['font-size'] || ctx['font-size'], + 'font-family': props['font-family'] || ctx['font-family'], + 'color': props['color'] || ctx['color'] + }; + } + + var defaultContext = { + 'font-style': 'normal', + 'font-variant': 'normal', + 'font-weight': 'normal', + 'font-size': '12px', + 'font-family': 'sans-serif', + 'color': 'black' + }; + + + // IMAGES + + function drawImage(redo, ctx, form) + { + var img = new Image(); + img.onload = redo; + img.src = form._3; + var w = form._0, + h = form._1, + pos = form._2, + srcX = pos._0, + srcY = pos._1, + srcW = w, + srcH = h, + destX = -w/2, + destY = -h/2, + destW = w, + destH = h; + + ctx.scale(1,-1); + ctx.drawImage(img, srcX, srcY, srcW, srcH, destX, destY, destW, destH); + } + + function renderForm(redo, ctx, form) + { + ctx.save(); + + var x = form.x, + y = form.y, + theta = form.theta, + scale = form.scale; + + if (x !== 0 || y !== 0) + { + ctx.translate(x, y); + } + if (theta !== 0) + { + ctx.rotate(theta); + } + if (scale !== 1) + { + ctx.scale(scale,scale); + } + if (form.alpha !== 1) + { + ctx.globalAlpha = ctx.globalAlpha * form.alpha; + } + + ctx.beginPath(); + var f = form.form; + switch (f.ctor) + { + case 'FPath': + drawLine(ctx, f._0, f._1); + break; + + case 'FImage': + drawImage(redo, ctx, f); + break; + + case 'FShape': + if (f._0.ctor === 'Line') + { + f._1.closed = true; + drawLine(ctx, f._0._0, f._1); + } + else + { + drawShape(redo, ctx, f._0._0, f._1); + } + break; + + case 'FText': + fillText(redo, ctx, f._0); + break; + + case 'FOutlinedText': + strokeText(redo, ctx, f._0, f._1); + break; + } + ctx.restore(); + } + + function formToMatrix(form) + { + var scale = form.scale; + var matrix = A6( Transform.matrix, scale, 0, 0, scale, form.x, form.y ); + + var theta = form.theta + if (theta !== 0) + { + matrix = A2( Transform.multiply, matrix, Transform.rotation(theta) ); + } + + return matrix; + } + + function str(n) + { + if (n < 0.00001 && n > -0.00001) + { + return 0; + } + return n; + } + + function makeTransform(w, h, form, matrices) + { + var props = form.form._0.props; + var m = A6( Transform.matrix, 1, 0, 0, -1, + (w - props.width ) / 2, + (h - props.height) / 2 ); + var len = matrices.length; + for (var i = 0; i < len; ++i) + { + m = A2( Transform.multiply, m, matrices[i] ); + } + m = A2( Transform.multiply, m, formToMatrix(form) ); + + return 'matrix(' + + str( m[0]) + ', ' + str( m[3]) + ', ' + + str(-m[1]) + ', ' + str(-m[4]) + ', ' + + str( m[2]) + ', ' + str( m[5]) + ')'; + } + + function stepperHelp(list) + { + var arr = List.toArray(list); + var i = 0; + function peekNext() + { + return i < arr.length ? arr[i].form.ctor : ''; + } + // assumes that there is a next element + function next() + { + var out = arr[i]; + ++i; + return out; + } + return { + peekNext: peekNext, + next: next + }; + } + + function formStepper(forms) + { + var ps = [stepperHelp(forms)]; + var matrices = []; + var alphas = []; + function peekNext() + { + var len = ps.length; + var formType = ''; + for (var i = 0; i < len; ++i ) + { + if (formType = ps[i].peekNext()) return formType; + } + return ''; + } + // assumes that there is a next element + function next(ctx) + { + while (!ps[0].peekNext()) + { + ps.shift(); + matrices.pop(); + alphas.shift(); + if (ctx) + { + ctx.restore(); + } + } + var out = ps[0].next(); + var f = out.form; + if (f.ctor === 'FGroup') + { + ps.unshift(stepperHelp(f._1)); + var m = A2(Transform.multiply, f._0, formToMatrix(out)); + ctx.save(); + ctx.transform(m[0], m[3], m[1], m[4], m[2], m[5]); + matrices.push(m); + + var alpha = (alphas[0] || 1) * out.alpha; + alphas.unshift(alpha); + ctx.globalAlpha = alpha; + } + return out; + } + function transforms() + { + return matrices; + } + function alpha() + { + return alphas[0] || 1; + } + return { + peekNext: peekNext, + next: next, + transforms: transforms, + alpha: alpha + }; + } + + function makeCanvas(w,h) + { + var canvas = NativeElement.createNode('canvas'); + canvas.style.width = w + 'px'; + canvas.style.height = h + 'px'; + canvas.style.display = "block"; + canvas.style.position = "absolute"; + var ratio = window.devicePixelRatio || 1; + canvas.width = w * ratio; + canvas.height = h * ratio; + return canvas; + } + + function render(model) + { + var div = NativeElement.createNode('div'); + div.style.overflow = 'hidden'; + div.style.position = 'relative'; + update(div, model, model); + return div; + } + + function nodeStepper(w,h,div) + { + var kids = div.childNodes; + var i = 0; + var ratio = window.devicePixelRatio || 1; + + function transform(transforms, ctx) + { + ctx.translate( w / 2 * ratio, h / 2 * ratio ); + ctx.scale( ratio, -ratio ); + var len = transforms.length; + for (var i = 0; i < len; ++i) + { + var m = transforms[i]; + ctx.save(); + ctx.transform(m[0], m[3], m[1], m[4], m[2], m[5]); + } + return ctx; + } + function nextContext(transforms) + { + while (i < kids.length) + { + var node = kids[i]; + if (node.getContext) + { + node.width = w * ratio; + node.height = h * ratio; + node.style.width = w + 'px'; + node.style.height = h + 'px'; + ++i; + return transform(transforms, node.getContext('2d')); + } + div.removeChild(node); + } + var canvas = makeCanvas(w,h); + div.appendChild(canvas); + // we have added a new node, so we must step our position + ++i; + return transform(transforms, canvas.getContext('2d')); + } + function addElement(matrices, alpha, form) + { + var kid = kids[i]; + var elem = form.form._0; + + var node = (!kid || kid.getContext) + ? NativeElement.render(elem) + : NativeElement.update(kid, kid.oldElement, elem); + + node.style.position = 'absolute'; + node.style.opacity = alpha * form.alpha * elem.props.opacity; + NativeElement.addTransform(node.style, makeTransform(w, h, form, matrices)); + node.oldElement = elem; + ++i; + if (!kid) + { + div.appendChild(node); + } + else + { + div.insertBefore(node, kid); + } + } + function clearRest() + { + while (i < kids.length) + { + div.removeChild(kids[i]); + } + } + return { + nextContext: nextContext, + addElement: addElement, + clearRest: clearRest + }; + } + + + function update(div, _, model) + { + var w = model.w; + var h = model.h; + + var forms = formStepper(model.forms); + var nodes = nodeStepper(w,h,div); + var ctx = null; + var formType = ''; + + while (formType = forms.peekNext()) + { + // make sure we have context if we need it + if (ctx === null && formType !== 'FElement') + { + ctx = nodes.nextContext(forms.transforms()); + ctx.globalAlpha = forms.alpha(); + } + + var form = forms.next(ctx); + // if it is FGroup, all updates are made within formStepper when next is called. + if (formType === 'FElement') + { + // update or insert an element, get a new context + nodes.addElement(forms.transforms(), forms.alpha(), form); + ctx = null; + } + else if (formType !== 'FGroup') + { + renderForm(function() { update(div, model, model); }, ctx, form); + } + } + nodes.clearRest(); + return div; + } + + + function collage(w,h,forms) + { + return A3(NativeElement.newElement, w, h, { + ctor: 'Custom', + type: 'Collage', + render: render, + update: update, + model: {w:w, h:h, forms:forms} + }); + } + + return localRuntime.Native.Graphics.Collage.values = { + collage: F3(collage) + }; + +}; + + +// setup +Elm.Native = Elm.Native || {}; +Elm.Native.Graphics = Elm.Native.Graphics || {}; +Elm.Native.Graphics.Element = Elm.Native.Graphics.Element || {}; + +// definition +Elm.Native.Graphics.Element.make = function(localRuntime) { + 'use strict'; + + // attempt to short-circuit + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Graphics = localRuntime.Native.Graphics || {}; + localRuntime.Native.Graphics.Element = localRuntime.Native.Graphics.Element || {}; + if ('values' in localRuntime.Native.Graphics.Element) + { + return localRuntime.Native.Graphics.Element.values; + } + + var Color = Elm.Native.Color.make(localRuntime); + var List = Elm.Native.List.make(localRuntime); + var Maybe = Elm.Maybe.make(localRuntime); + var Text = Elm.Native.Text.make(localRuntime); + var Utils = Elm.Native.Utils.make(localRuntime); + + + // CREATION + + function createNode(elementType) + { + var node = document.createElement(elementType); + node.style.padding = "0"; + node.style.margin = "0"; + return node; + } + + + function newElement(width, height, elementPrim) + { + return { + _: {}, + element: elementPrim, + props: { + _: {}, + id: Utils.guid(), + width: width, + height: height, + opacity: 1, + color: Maybe.Nothing, + href: "", + tag: "", + hover: Utils.Tuple0, + click: Utils.Tuple0 + } + }; + } + + + // PROPERTIES + + function setProps(elem, node) + { + var props = elem.props; + + var element = elem.element; + var width = props.width - (element.adjustWidth || 0); + var height = props.height - (element.adjustHeight || 0); + node.style.width = (width |0) + 'px'; + node.style.height = (height|0) + 'px'; + + if (props.opacity !== 1) + { + node.style.opacity = props.opacity; + } + + if (props.color.ctor === 'Just') + { + node.style.backgroundColor = Color.toCss(props.color._0); + } + + if (props.tag !== '') + { + node.id = props.tag; + } + + if (props.hover.ctor !== '_Tuple0') + { + addHover(node, props.hover); + } + + if (props.click.ctor !== '_Tuple0') + { + addClick(node, props.click); + } + + if (props.href !== '') + { + var anchor = createNode('a'); + anchor.href = props.href; + anchor.style.display = 'block'; + anchor.style.pointerEvents = 'auto'; + anchor.appendChild(node); + node = anchor; + } + + return node; + } + + function addClick(e, handler) + { + e.style.pointerEvents = 'auto'; + e.elm_click_handler = handler; + function trigger(ev) + { + e.elm_click_handler(Utils.Tuple0); + ev.stopPropagation(); + } + e.elm_click_trigger = trigger; + e.addEventListener('click', trigger); + } + + function removeClick(e, handler) + { + if (e.elm_click_trigger) + { + e.removeEventListener('click', e.elm_click_trigger); + e.elm_click_trigger = null; + e.elm_click_handler = null; + } + } + + function addHover(e, handler) + { + e.style.pointerEvents = 'auto'; + e.elm_hover_handler = handler; + e.elm_hover_count = 0; + + function over(evt) + { + if (e.elm_hover_count++ > 0) return; + e.elm_hover_handler(true); + evt.stopPropagation(); + } + function out(evt) + { + if (e.contains(evt.toElement || evt.relatedTarget)) return; + e.elm_hover_count = 0; + e.elm_hover_handler(false); + evt.stopPropagation(); + } + e.elm_hover_over = over; + e.elm_hover_out = out; + e.addEventListener('mouseover', over); + e.addEventListener('mouseout', out); + } + + function removeHover(e) + { + e.elm_hover_handler = null; + if (e.elm_hover_over) + { + e.removeEventListener('mouseover', e.elm_hover_over); + e.elm_hover_over = null; + } + if (e.elm_hover_out) + { + e.removeEventListener('mouseout', e.elm_hover_out); + e.elm_hover_out = null; + } + } + + + // IMAGES + + function image(props, img) + { + switch (img._0.ctor) + { + case 'Plain': + return plainImage(img._3); + + case 'Fitted': + return fittedImage(props.width, props.height, img._3); + + case 'Cropped': + return croppedImage(img,props.width,props.height,img._3); + + case 'Tiled': + return tiledImage(img._3); + } + } + + function plainImage(src) + { + var img = createNode('img'); + img.src = src; + img.name = src; + img.style.display = "block"; + return img; + } + + function tiledImage(src) + { + var div = createNode('div'); + div.style.backgroundImage = 'url(' + src + ')'; + return div; + } + + function fittedImage(w, h, src) + { + var div = createNode('div'); + div.style.background = 'url(' + src + ') no-repeat center'; + div.style.webkitBackgroundSize = 'cover'; + div.style.MozBackgroundSize = 'cover'; + div.style.OBackgroundSize = 'cover'; + div.style.backgroundSize = 'cover'; + return div; + } + + function croppedImage(elem, w, h, src) + { + var pos = elem._0._0; + var e = createNode('div'); + e.style.overflow = "hidden"; + + var img = createNode('img'); + img.onload = function() { + var sw = w / elem._1, sh = h / elem._2; + img.style.width = ((this.width * sw)|0) + 'px'; + img.style.height = ((this.height * sh)|0) + 'px'; + img.style.marginLeft = ((- pos._0 * sw)|0) + 'px'; + img.style.marginTop = ((- pos._1 * sh)|0) + 'px'; + }; + img.src = src; + img.name = src; + e.appendChild(img); + return e; + } + + + // FLOW + + function goOut(node) + { + node.style.position = 'absolute'; + return node; + } + function goDown(node) + { + return node; + } + function goRight(node) + { + node.style.styleFloat = 'left'; + node.style.cssFloat = 'left'; + return node; + } + + var directionTable = { + DUp : goDown, + DDown : goDown, + DLeft : goRight, + DRight : goRight, + DIn : goOut, + DOut : goOut + }; + function needsReversal(dir) + { + return dir == 'DUp' || dir == 'DLeft' || dir == 'DIn'; + } + + function flow(dir,elist) + { + var array = List.toArray(elist); + var container = createNode('div'); + var goDir = directionTable[dir]; + if (goDir == goOut) + { + container.style.pointerEvents = 'none'; + } + if (needsReversal(dir)) + { + array.reverse(); + } + var len = array.length; + for (var i = 0; i < len; ++i) + { + container.appendChild(goDir(render(array[i]))); + } + return container; + } + + + // CONTAINER + + function toPos(pos) + { + return pos.ctor === "Absolute" + ? pos._0 + "px" + : (pos._0 * 100) + "%"; + } + + // must clear right, left, top, bottom, and transform + // before calling this function + function setPos(pos,elem,e) + { + var element = elem.element; + var props = elem.props; + var w = props.width + (element.adjustWidth ? element.adjustWidth : 0); + var h = props.height + (element.adjustHeight ? element.adjustHeight : 0); + + e.style.position = 'absolute'; + e.style.margin = 'auto'; + var transform = ''; + + switch (pos.horizontal.ctor) + { + case 'P': + e.style.right = toPos(pos.x); + e.style.removeProperty('left'); + break; + + case 'Z': + transform = 'translateX(' + ((-w/2)|0) + 'px) '; + + case 'N': + e.style.left = toPos(pos.x); + e.style.removeProperty('right'); + break; + } + switch (pos.vertical.ctor) + { + case 'N': + e.style.bottom = toPos(pos.y); + e.style.removeProperty('top'); + break; + + case 'Z': + transform += 'translateY(' + ((-h/2)|0) + 'px)'; + + case 'P': + e.style.top = toPos(pos.y); + e.style.removeProperty('bottom'); + break; + } + if (transform !== '') + { + addTransform(e.style, transform); + } + return e; + } + + function addTransform(style, transform) + { + style.transform = transform; + style.msTransform = transform; + style.MozTransform = transform; + style.webkitTransform = transform; + style.OTransform = transform; + } + + function container(pos,elem) + { + var e = render(elem); + setPos(pos, elem, e); + var div = createNode('div'); + div.style.position = 'relative'; + div.style.overflow = 'hidden'; + div.appendChild(e); + return div; + } + + + function rawHtml(elem) + { + var html = elem.html; + var guid = elem.guid; + var align = elem.align; + + var div = createNode('div'); + div.innerHTML = html; + div.style.visibility = "hidden"; + if (align) + { + div.style.textAlign = align; + } + div.style.visibility = 'visible'; + div.style.pointerEvents = 'auto'; + return div; + } + + + // RENDER + + function render(elem) + { + return setProps(elem, makeElement(elem)); + } + function makeElement(e) + { + var elem = e.element; + switch(elem.ctor) + { + case 'Image': + return image(e.props, elem); + + case 'Flow': + return flow(elem._0.ctor, elem._1); + + case 'Container': + return container(elem._0, elem._1); + + case 'Spacer': + return createNode('div'); + + case 'RawHtml': + return rawHtml(elem); + + case 'Custom': + return elem.render(elem.model); + } + } + + function updateAndReplace(node, curr, next) + { + var newNode = update(node, curr, next); + if (newNode !== node) + { + node.parentNode.replaceChild(newNode, node); + } + return newNode; + } + + + // UPDATE + + function update(node, curr, next) + { + var rootNode = node; + if (node.tagName === 'A') + { + node = node.firstChild; + } + if (curr.props.id === next.props.id) + { + updateProps(node, curr, next); + return rootNode; + } + if (curr.element.ctor !== next.element.ctor) + { + return render(next); + } + var nextE = next.element; + var currE = curr.element; + switch(nextE.ctor) + { + case "Spacer": + updateProps(node, curr, next); + return rootNode; + + case "RawHtml": + if(currE.html.valueOf() !== nextE.html.valueOf()) + { + node.innerHTML = nextE.html; + } + updateProps(node, curr, next); + return rootNode; + + case "Image": + if (nextE._0.ctor === 'Plain') + { + if (nextE._3 !== currE._3) + { + node.src = nextE._3; + } + } + else if (!Utils.eq(nextE,currE) + || next.props.width !== curr.props.width + || next.props.height !== curr.props.height) + { + return render(next); + } + updateProps(node, curr, next); + return rootNode; + + case "Flow": + var arr = List.toArray(nextE._1); + for (var i = arr.length; i--; ) + { + arr[i] = arr[i].element.ctor; + } + if (nextE._0.ctor !== currE._0.ctor) + { + return render(next); + } + var nexts = List.toArray(nextE._1); + var kids = node.childNodes; + if (nexts.length !== kids.length) + { + return render(next); + } + var currs = List.toArray(currE._1); + var dir = nextE._0.ctor; + var goDir = directionTable[dir]; + var toReverse = needsReversal(dir); + var len = kids.length; + for (var i = len; i-- ;) + { + var subNode = kids[toReverse ? len - i - 1 : i]; + goDir(updateAndReplace(subNode, currs[i], nexts[i])); + } + updateProps(node, curr, next); + return rootNode; + + case "Container": + var subNode = node.firstChild; + var newSubNode = updateAndReplace(subNode, currE._1, nextE._1); + setPos(nextE._0, nextE._1, newSubNode); + updateProps(node, curr, next); + return rootNode; + + case "Custom": + if (currE.type === nextE.type) + { + var updatedNode = nextE.update(node, currE.model, nextE.model); + updateProps(updatedNode, curr, next); + return updatedNode; + } + return render(next); + } + } + + function updateProps(node, curr, next) + { + var nextProps = next.props; + var currProps = curr.props; + + var element = next.element; + var width = nextProps.width - (element.adjustWidth || 0); + var height = nextProps.height - (element.adjustHeight || 0); + if (width !== currProps.width) + { + node.style.width = (width|0) + 'px'; + } + if (height !== currProps.height) + { + node.style.height = (height|0) + 'px'; + } + + if (nextProps.opacity !== currProps.opacity) + { + node.style.opacity = nextProps.opacity; + } + + var nextColor = nextProps.color.ctor === 'Just' + ? Color.toCss(nextProps.color._0) + : ''; + if (node.style.backgroundColor !== nextColor) + { + node.style.backgroundColor = nextColor; + } + + if (nextProps.tag !== currProps.tag) + { + node.id = nextProps.tag; + } + + if (nextProps.href !== currProps.href) + { + if (currProps.href === '') + { + // add a surrounding href + var anchor = createNode('a'); + anchor.href = nextProps.href; + anchor.style.display = 'block'; + anchor.style.pointerEvents = 'auto'; + + node.parentNode.replaceChild(anchor, node); + anchor.appendChild(node); + } + else if (nextProps.href === '') + { + // remove the surrounding href + var anchor = node.parentNode; + anchor.parentNode.replaceChild(node, anchor); + } + else + { + // just update the link + node.parentNode.href = nextProps.href; + } + } + + // update click and hover handlers + var removed = false; + + // update hover handlers + if (currProps.hover.ctor === '_Tuple0') + { + if (nextProps.hover.ctor !== '_Tuple0') + { + addHover(node, nextProps.hover); + } + } + else + { + if (nextProps.hover.ctor === '_Tuple0') + { + removed = true; + removeHover(node); + } + else + { + node.elm_hover_handler = nextProps.hover; + } + } + + // update click handlers + if (currProps.click.ctor === '_Tuple0') + { + if (nextProps.click.ctor !== '_Tuple0') + { + addClick(node, nextProps.click); + } + } + else + { + if (nextProps.click.ctor === '_Tuple0') + { + removed = true; + removeClick(node); + } + else + { + node.elm_click_handler = nextProps.click; + } + } + + // stop capturing clicks if + if (removed + && nextProps.hover.ctor === '_Tuple0' + && nextProps.click.ctor === '_Tuple0') + { + node.style.pointerEvents = 'none'; + } + } + + + // TEXT + + function block(align) + { + return function(text) + { + var raw = { + ctor :'RawHtml', + html : Text.renderHtml(text), + align: align + }; + var pos = htmlHeight(0, raw); + return newElement(pos._0, pos._1, raw); + } + } + + function markdown(text) + { + var raw = { + ctor:'RawHtml', + html: text, + align: null + }; + var pos = htmlHeight(0, raw); + return newElement(pos._0, pos._1, raw); + } + + function htmlHeight(width, rawHtml) + { + // create dummy node + var temp = document.createElement('div'); + temp.innerHTML = rawHtml.html; + if (width > 0) + { + temp.style.width = width + "px"; + } + temp.style.visibility = "hidden"; + temp.style.styleFloat = "left"; + temp.style.cssFloat = "left"; + + document.body.appendChild(temp); + + // get dimensions + var style = window.getComputedStyle(temp, null); + var w = Math.ceil(style.getPropertyValue("width").slice(0,-2) - 0); + var h = Math.ceil(style.getPropertyValue("height").slice(0,-2) - 0); + document.body.removeChild(temp); + return Utils.Tuple2(w,h); + } + + + return localRuntime.Native.Graphics.Element.values = { + render: render, + update: update, + updateAndReplace: updateAndReplace, + + createNode: createNode, + newElement: F3(newElement), + addTransform: addTransform, + htmlHeight: F2(htmlHeight), + guid: Utils.guid, + + block: block, + markdown: markdown + }; + +}; + +// setup +Elm.Native = Elm.Native || {}; +Elm.Native.Graphics = Elm.Native.Graphics || {}; +Elm.Native.Graphics.Input = Elm.Native.Graphics.Input || {}; + +// definition +Elm.Native.Graphics.Input.make = function(localRuntime) { + 'use strict'; + + // attempt to short-circuit + if ('values' in Elm.Native.Graphics.Input) { + return Elm.Native.Graphics.Input.values; + } + + var Color = Elm.Native.Color.make(localRuntime); + var List = Elm.Native.List.make(localRuntime); + var Signal = Elm.Native.Signal.make(localRuntime); + var Text = Elm.Native.Text.make(localRuntime); + var Utils = Elm.Native.Utils.make(localRuntime); + + var Element = Elm.Native.Graphics.Element.make(localRuntime); + + + function renderDropDown(model) + { + var drop = Element.createNode('select'); + drop.style.border = '0 solid'; + drop.style.pointerEvents = 'auto'; + drop.style.display = 'block'; + + drop.elm_values = List.toArray(model.values); + drop.elm_handler = model.handler; + var values = drop.elm_values; + + for (var i = 0; i < values.length; ++i) + { + var option = Element.createNode('option'); + var name = values[i]._0; + option.value = name; + option.innerHTML = name; + drop.appendChild(option); + } + drop.addEventListener('change', function() { + Signal.sendMessage(drop.elm_handler(drop.elm_values[drop.selectedIndex]._1)); + }); + + return drop; + } + + function updateDropDown(node, oldModel, newModel) + { + node.elm_values = List.toArray(newModel.values); + node.elm_handler = newModel.handler; + + var values = node.elm_values; + var kids = node.childNodes; + var kidsLength = kids.length; + + var i = 0; + for (; i < kidsLength && i < values.length; ++i) + { + var option = kids[i]; + var name = values[i]._0; + option.value = name; + option.innerHTML = name; + } + for (; i < kidsLength; ++i) + { + node.removeChild(node.lastChild); + } + for (; i < values.length; ++i) + { + var option = Element.createNode('option'); + var name = values[i]._0; + option.value = name; + option.innerHTML = name; + node.appendChild(option); + } + return node; + } + + function dropDown(handler, values) + { + return A3(Element.newElement, 100, 24, { + ctor: 'Custom', + type: 'DropDown', + render: renderDropDown, + update: updateDropDown, + model: { + values: values, + handler: handler + } + }); + } + + function renderButton(model) + { + var node = Element.createNode('button'); + node.style.display = 'block'; + node.style.pointerEvents = 'auto'; + node.elm_message = model.message; + function click() + { + Signal.sendMessage(node.elm_message); + } + node.addEventListener('click', click); + node.innerHTML = model.text; + return node; + } + + function updateButton(node, oldModel, newModel) + { + node.elm_message = newModel.message; + var txt = newModel.text; + if (oldModel.text !== txt) + { + node.innerHTML = txt; + } + return node; + } + + function button(message, text) + { + return A3(Element.newElement, 100, 40, { + ctor: 'Custom', + type: 'Button', + render: renderButton, + update: updateButton, + model: { + message: message, + text:text + } + }); + } + + function renderCustomButton(model) + { + var btn = Element.createNode('div'); + btn.style.pointerEvents = 'auto'; + btn.elm_message = model.message; + + btn.elm_up = Element.render(model.up); + btn.elm_hover = Element.render(model.hover); + btn.elm_down = Element.render(model.down); + + btn.elm_up.style.display = 'block'; + btn.elm_hover.style.display = 'none'; + btn.elm_down.style.display = 'none'; + + btn.appendChild(btn.elm_up); + btn.appendChild(btn.elm_hover); + btn.appendChild(btn.elm_down); + + function swap(visibleNode, hiddenNode1, hiddenNode2) + { + visibleNode.style.display = 'block'; + hiddenNode1.style.display = 'none'; + hiddenNode2.style.display = 'none'; + } + + var overCount = 0; + function over(e) + { + if (overCount++ > 0) return; + swap(btn.elm_hover, btn.elm_down, btn.elm_up); + } + function out(e) + { + if (btn.contains(e.toElement || e.relatedTarget)) return; + overCount = 0; + swap(btn.elm_up, btn.elm_down, btn.elm_hover); + } + function up() + { + swap(btn.elm_hover, btn.elm_down, btn.elm_up); + Signal.sendMessage(btn.elm_message); + } + function down() + { + swap(btn.elm_down, btn.elm_hover, btn.elm_up); + } + + btn.addEventListener('mouseover', over); + btn.addEventListener('mouseout' , out); + btn.addEventListener('mousedown', down); + btn.addEventListener('mouseup' , up); + + return btn; + } + + function updateCustomButton(node, oldModel, newModel) + { + node.elm_message = newModel.message; + + var kids = node.childNodes; + var styleUp = kids[0].style.display; + var styleHover = kids[1].style.display; + var styleDown = kids[2].style.display; + + Element.updateAndReplace(kids[0], oldModel.up, newModel.up); + Element.updateAndReplace(kids[1], oldModel.hover, newModel.hover); + Element.updateAndReplace(kids[2], oldModel.down, newModel.down); + + var kids = node.childNodes; + kids[0].style.display = styleUp; + kids[1].style.display = styleHover; + kids[2].style.display = styleDown; + + return node; + } + + function max3(a,b,c) + { + var ab = a > b ? a : b; + return ab > c ? ab : c; + } + + function customButton(message, up, hover, down) + { + return A3(Element.newElement, + max3(up.props.width, hover.props.width, down.props.width), + max3(up.props.height, hover.props.height, down.props.height), + { ctor: 'Custom', + type: 'CustomButton', + render: renderCustomButton, + update: updateCustomButton, + model: { + message: message, + up: up, + hover: hover, + down: down + } + }); + } + + function renderCheckbox(model) + { + var node = Element.createNode('input'); + node.type = 'checkbox'; + node.checked = model.checked; + node.style.display = 'block'; + node.style.pointerEvents = 'auto'; + node.elm_handler = model.handler; + function change() + { + Signal.sendMessage(node.elm_handler(node.checked)); + } + node.addEventListener('change', change); + return node; + } + + function updateCheckbox(node, oldModel, newModel) + { + node.elm_handler = newModel.handler; + node.checked = newModel.checked; + return node; + } + + function checkbox(handler, checked) + { + return A3(Element.newElement, 13, 13, { + ctor: 'Custom', + type: 'CheckBox', + render: renderCheckbox, + update: updateCheckbox, + model: { handler:handler, checked:checked } + }); + } + + function setRange(node, start, end, dir) + { + if (node.parentNode) + { + node.setSelectionRange(start, end, dir); + } + else + { + setTimeout(function(){node.setSelectionRange(start, end, dir);}, 0); + } + } + + function updateIfNeeded(css, attribute, latestAttribute) + { + if (css[attribute] !== latestAttribute) + { + css[attribute] = latestAttribute; + } + } + function cssDimensions(dimensions) + { + return dimensions.top + 'px ' + + dimensions.right + 'px ' + + dimensions.bottom + 'px ' + + dimensions.left + 'px'; + } + function updateFieldStyle(css, style) + { + updateIfNeeded(css, 'padding', cssDimensions(style.padding)); + + var outline = style.outline; + updateIfNeeded(css, 'border-width', cssDimensions(outline.width)); + updateIfNeeded(css, 'border-color', Color.toCss(outline.color)); + updateIfNeeded(css, 'border-radius', outline.radius + 'px'); + + var highlight = style.highlight; + if (highlight.width === 0) + { + css.outline = 'none'; + } + else + { + updateIfNeeded(css, 'outline-width', highlight.width + 'px'); + updateIfNeeded(css, 'outline-color', Color.toCss(highlight.color)); + } + + var textStyle = style.style; + updateIfNeeded(css, 'color', Color.toCss(textStyle.color)); + if (textStyle.typeface.ctor !== '[]') + { + updateIfNeeded(css, 'font-family', Text.toTypefaces(textStyle.typeface)); + } + if (textStyle.height.ctor !== "Nothing") + { + updateIfNeeded(css, 'font-size', textStyle.height._0 + 'px'); + } + updateIfNeeded(css, 'font-weight', textStyle.bold ? 'bold' : 'normal'); + updateIfNeeded(css, 'font-style', textStyle.italic ? 'italic' : 'normal'); + if (textStyle.line.ctor !== 'Nothing') + { + updateIfNeeded(css, 'text-decoration', Text.toLine(textStyle.line._0)); + } + } + + function renderField(model) + { + var field = Element.createNode('input'); + updateFieldStyle(field.style, model.style); + field.style.borderStyle = 'solid'; + field.style.pointerEvents = 'auto'; + + field.type = model.type; + field.placeholder = model.placeHolder; + field.value = model.content.string; + + field.elm_handler = model.handler; + field.elm_old_value = field.value; + + function inputUpdate(event) + { + var curr = field.elm_old_value; + var next = field.value; + if (curr === next) + { + return; + } + + var direction = field.selectionDirection === 'forward' ? 'Forward' : 'Backward'; + var start = field.selectionStart; + var end = field.selectionEnd; + field.value = field.elm_old_value; + + Signal.sendMessage(field.elm_handler({ + _:{}, + string: next, + selection: { + _:{}, + start: start, + end: end, + direction: { ctor: direction } + } + })); + } + + field.addEventListener('input', inputUpdate); + field.addEventListener('focus', function() { + field.elm_hasFocus = true; + }); + field.addEventListener('blur', function() { + field.elm_hasFocus = false; + }); + + return field; + } + + function updateField(field, oldModel, newModel) + { + if (oldModel.style !== newModel.style) + { + updateFieldStyle(field.style, newModel.style); + } + field.elm_handler = newModel.handler; + + field.type = newModel.type; + field.placeholder = newModel.placeHolder; + var value = newModel.content.string; + field.value = value; + field.elm_old_value = value; + if (field.elm_hasFocus) + { + var selection = newModel.content.selection; + var direction = selection.direction.ctor === 'Forward' ? 'forward' : 'backward'; + setRange(field, selection.start, selection.end, direction); + } + return field; + } + + function mkField(type) + { + function field(style, handler, placeHolder, content) + { + var padding = style.padding; + var outline = style.outline.width; + var adjustWidth = padding.left + padding.right + outline.left + outline.right; + var adjustHeight = padding.top + padding.bottom + outline.top + outline.bottom; + return A3(Element.newElement, 200, 30, { + ctor: 'Custom', + type: type + 'Field', + adjustWidth: adjustWidth, + adjustHeight: adjustHeight, + render: renderField, + update: updateField, + model: { + handler:handler, + placeHolder:placeHolder, + content:content, + style:style, + type:type + } + }); + } + return F4(field); + } + + function hoverable(handler, elem) + { + function onHover(bool) + { + Signal.sendMessage(handler(bool)); + } + var props = Utils.replace([['hover',onHover]], elem.props); + return { + props: props, + element: elem.element + }; + } + + function clickable(message, elem) + { + function onClick() + { + Signal.sendMessage(message); + } + var props = Utils.replace([['click',onClick]], elem.props); + return { + props: props, + element: elem.element + }; + } + + return Elm.Native.Graphics.Input.values = { + button: F2(button), + customButton: F4(customButton), + checkbox: F2(checkbox), + dropDown: F2(dropDown), + field: mkField('text'), + email: mkField('email'), + password: mkField('password'), + hoverable: F2(hoverable), + clickable: F2(clickable) + }; + +}; + +Elm.Native.Json = {}; +Elm.Native.Json.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Json = localRuntime.Native.Json || {}; + if (localRuntime.Native.Json.values) { + return localRuntime.Native.Json.values; + } + + var ElmArray = Elm.Native.Array.make(localRuntime); + var List = Elm.Native.List.make(localRuntime); + var Maybe = Elm.Maybe.make(localRuntime); + var Result = Elm.Result.make(localRuntime); + var Utils = Elm.Native.Utils.make(localRuntime); + + + function crash(expected, actual) { + throw new Error( + 'expecting ' + expected + ' but got ' + JSON.stringify(actual) + ); + } + + + // PRIMITIVE VALUES + + function decodeNull(successValue) { + return function(value) { + if (value === null) { + return successValue; + } + crash('null', value); + }; + } + + + function decodeString(value) { + if (typeof value === 'string' || value instanceof String) { + return value; + } + crash('a String', value); + } + + + function decodeFloat(value) { + if (typeof value === 'number') { + return value; + } + crash('a Float', value); + } + + + function decodeInt(value) { + if (typeof value === 'number' && (value|0) === value) { + return value; + } + crash('an Int', value); + } + + + function decodeBool(value) { + if (typeof value === 'boolean') { + return value; + } + crash('a Bool', value); + } + + + // ARRAY + + function decodeArray(decoder) { + return function(value) { + if (value instanceof Array) { + var len = value.length; + var array = new Array(len); + for (var i = len; i-- ; ) { + array[i] = decoder(value[i]); + } + return ElmArray.fromJSArray(array); + } + crash('an Array', value); + }; + } + + + // LIST + + function decodeList(decoder) { + return function(value) { + if (value instanceof Array) { + var len = value.length; + var list = List.Nil; + for (var i = len; i-- ; ) { + list = List.Cons( decoder(value[i]), list ); + } + return list; + } + crash('a List', value); + }; + } + + + // MAYBE + + function decodeMaybe(decoder) { + return function(value) { + try { + return Maybe.Just(decoder(value)); + } catch(e) { + return Maybe.Nothing; + } + }; + } + + + // FIELDS + + function decodeField(field, decoder) { + return function(value) { + var subValue = value[field]; + if (subValue !== undefined) { + return decoder(subValue); + } + crash("an object with field '" + field + "'", value); + }; + } + + + // OBJECTS + + function decodeKeyValuePairs(decoder) { + return function(value) { + var isObject = + typeof value === 'object' + && value !== null + && !(value instanceof Array); + + if (isObject) { + var keyValuePairs = List.Nil; + for (var key in value) { + var elmValue = decoder(value[key]); + var pair = Utils.Tuple2(key, elmValue); + keyValuePairs = List.Cons(pair, keyValuePairs); + } + return keyValuePairs; + } + + crash("an object", value); + }; + } + + function decodeObject1(f, d1) { + return function(value) { + return f(d1(value)); + }; + } + + function decodeObject2(f, d1, d2) { + return function(value) { + return A2( f, d1(value), d2(value) ); + }; + } + + function decodeObject3(f, d1, d2, d3) { + return function(value) { + return A3( f, d1(value), d2(value), d3(value) ); + }; + } + + function decodeObject4(f, d1, d2, d3, d4) { + return function(value) { + return A4( f, d1(value), d2(value), d3(value), d4(value) ); + }; + } + + function decodeObject5(f, d1, d2, d3, d4, d5) { + return function(value) { + return A5( f, d1(value), d2(value), d3(value), d4(value), d5(value) ); + }; + } + + function decodeObject6(f, d1, d2, d3, d4, d5, d6) { + return function(value) { + return A6( f, + d1(value), + d2(value), + d3(value), + d4(value), + d5(value), + d6(value) + ); + }; + } + + function decodeObject7(f, d1, d2, d3, d4, d5, d6, d7) { + return function(value) { + return A7( f, + d1(value), + d2(value), + d3(value), + d4(value), + d5(value), + d6(value), + d7(value) + ); + }; + } + + function decodeObject8(f, d1, d2, d3, d4, d5, d6, d7, d8) { + return function(value) { + return A8( f, + d1(value), + d2(value), + d3(value), + d4(value), + d5(value), + d6(value), + d7(value), + d8(value) + ); + }; + } + + + // TUPLES + + function decodeTuple1(f, d1) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 1 ) { + crash('a Tuple of length 1', value); + } + return f( d1(value[0]) ); + }; + } + + function decodeTuple2(f, d1, d2) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 2 ) { + crash('a Tuple of length 2', value); + } + return A2( f, d1(value[0]), d2(value[1]) ); + }; + } + + function decodeTuple3(f, d1, d2, d3) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 3 ) { + crash('a Tuple of length 3', value); + } + return A3( f, d1(value[0]), d2(value[1]), d3(value[2]) ); + }; + } + + + function decodeTuple4(f, d1, d2, d3, d4) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 4 ) { + crash('a Tuple of length 4', value); + } + return A4( f, d1(value[0]), d2(value[1]), d3(value[2]), d4(value[3]) ); + }; + } + + + function decodeTuple5(f, d1, d2, d3, d4, d5) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 5 ) { + crash('a Tuple of length 5', value); + } + return A5( f, + d1(value[0]), + d2(value[1]), + d3(value[2]), + d4(value[3]), + d5(value[4]) + ); + }; + } + + + function decodeTuple6(f, d1, d2, d3, d4, d5, d6) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 6 ) { + crash('a Tuple of length 6', value); + } + return A6( f, + d1(value[0]), + d2(value[1]), + d3(value[2]), + d4(value[3]), + d5(value[4]), + d6(value[5]) + ); + }; + } + + function decodeTuple7(f, d1, d2, d3, d4, d5, d6, d7) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 7 ) { + crash('a Tuple of length 7', value); + } + return A7( f, + d1(value[0]), + d2(value[1]), + d3(value[2]), + d4(value[3]), + d5(value[4]), + d6(value[5]), + d7(value[6]) + ); + }; + } + + + function decodeTuple8(f, d1, d2, d3, d4, d5, d6, d7, d8) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 8 ) { + crash('a Tuple of length 8', value); + } + return A8( f, + d1(value[0]), + d2(value[1]), + d3(value[2]), + d4(value[3]), + d5(value[4]), + d6(value[5]), + d7(value[6]), + d8(value[7]) + ); + }; + } + + + // CUSTOM DECODERS + + function decodeValue(value) { + return value; + } + + function runDecoderValue(decoder, value) { + try { + return Result.Ok(decoder(value)); + } catch(e) { + return Result.Err(e.message); + } + } + + function customDecoder(decoder, callback) { + return function(value) { + var result = callback(decoder(value)); + if (result.ctor === 'Err') { + throw new Error('custom decoder failed: ' + result._0); + } + return result._0; + } + } + + function andThen(decode, callback) { + return function(value) { + var result = decode(value); + return callback(result)(value); + } + } + + function fail(msg) { + return function(value) { + throw new Error(msg); + } + } + + function succeed(successValue) { + return function(value) { + return successValue; + } + } + + + // ONE OF MANY + + function oneOf(decoders) { + return function(value) { + var errors = []; + var temp = decoders; + while (temp.ctor !== '[]') { + try { + return temp._0(value); + } catch(e) { + errors.push(e.message); + } + temp = temp._1; + } + throw new Error('expecting one of the following:\n ' + errors.join('\n ')); + } + } + + function get(decoder, value) { + try { + return Result.Ok(decoder(value)); + } catch(e) { + return Result.Err(e.message); + } + } + + + // ENCODE / DECODE + + function runDecoderString(decoder, string) { + try { + return Result.Ok(decoder(JSON.parse(string))); + } catch(e) { + return Result.Err(e.message); + } + } + + function encode(indentLevel, value) { + return JSON.stringify(value, null, indentLevel); + } + + function identity(value) { + return value; + } + + function encodeObject(keyValuePairs) { + var obj = {}; + while (keyValuePairs.ctor !== '[]') { + var pair = keyValuePairs._0; + obj[pair._0] = pair._1; + keyValuePairs = keyValuePairs._1; + } + return obj; + } + + return localRuntime.Native.Json.values = { + encode: F2(encode), + runDecoderString: F2(runDecoderString), + runDecoderValue: F2(runDecoderValue), + + get: F2(get), + oneOf: oneOf, + + decodeNull: decodeNull, + decodeInt: decodeInt, + decodeFloat: decodeFloat, + decodeString: decodeString, + decodeBool: decodeBool, + + decodeMaybe: decodeMaybe, + + decodeList: decodeList, + decodeArray: decodeArray, + + decodeField: F2(decodeField), + + decodeObject1: F2(decodeObject1), + decodeObject2: F3(decodeObject2), + decodeObject3: F4(decodeObject3), + decodeObject4: F5(decodeObject4), + decodeObject5: F6(decodeObject5), + decodeObject6: F7(decodeObject6), + decodeObject7: F8(decodeObject7), + decodeObject8: F9(decodeObject8), + decodeKeyValuePairs: decodeKeyValuePairs, + + decodeTuple1: F2(decodeTuple1), + decodeTuple2: F3(decodeTuple2), + decodeTuple3: F4(decodeTuple3), + decodeTuple4: F5(decodeTuple4), + decodeTuple5: F6(decodeTuple5), + decodeTuple6: F7(decodeTuple6), + decodeTuple7: F8(decodeTuple7), + decodeTuple8: F9(decodeTuple8), + + andThen: F2(andThen), + decodeValue: decodeValue, + customDecoder: F2(customDecoder), + fail: fail, + succeed: succeed, + + identity: identity, + encodeNull: null, + encodeArray: ElmArray.toJSArray, + encodeList: List.toArray, + encodeObject: encodeObject + + }; + +}; + +Elm.Native.List = {}; +Elm.Native.List.make = function(localRuntime) { + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.List = localRuntime.Native.List || {}; + if (localRuntime.Native.List.values) + { + return localRuntime.Native.List.values; + } + if ('values' in Elm.Native.List) + { + return localRuntime.Native.List.values = Elm.Native.List.values; + } + + var Utils = Elm.Native.Utils.make(localRuntime); + + var Nil = Utils.Nil; + var Cons = Utils.Cons; + + function toArray(xs) + { + var out = []; + while (xs.ctor !== '[]') + { + out.push(xs._0); + xs = xs._1; + } + return out; + } + + function fromArray(arr) + { + var out = Nil; + for (var i = arr.length; i--; ) + { + out = Cons(arr[i], out); + } + return out; + } + + function range(lo,hi) + { + var lst = Nil; + if (lo <= hi) + { + do { lst = Cons(hi,lst) } while (hi-->lo); + } + return lst + } + + // f defined similarly for both foldl and foldr (NB: different from Haskell) + // ie, foldl : (a -> b -> b) -> b -> [a] -> b + function foldl(f, b, xs) + { + var acc = b; + while (xs.ctor !== '[]') + { + acc = A2(f, xs._0, acc); + xs = xs._1; + } + return acc; + } + + function foldr(f, b, xs) + { + var arr = toArray(xs); + var acc = b; + for (var i = arr.length; i--; ) + { + acc = A2(f, arr[i], acc); + } + return acc; + } + + function any(pred, xs) + { + while (xs.ctor !== '[]') + { + if (pred(xs._0)) + { + return true; + } + xs = xs._1; + } + return false; + } + + function map2(f, xs, ys) + { + var arr = []; + while (xs.ctor !== '[]' && ys.ctor !== '[]') + { + arr.push(A2(f, xs._0, ys._0)); + xs = xs._1; + ys = ys._1; + } + return fromArray(arr); + } + + function map3(f, xs, ys, zs) + { + var arr = []; + while (xs.ctor !== '[]' && ys.ctor !== '[]' && zs.ctor !== '[]') + { + arr.push(A3(f, xs._0, ys._0, zs._0)); + xs = xs._1; + ys = ys._1; + zs = zs._1; + } + return fromArray(arr); + } + + function map4(f, ws, xs, ys, zs) + { + var arr = []; + while ( ws.ctor !== '[]' + && xs.ctor !== '[]' + && ys.ctor !== '[]' + && zs.ctor !== '[]') + { + arr.push(A4(f, ws._0, xs._0, ys._0, zs._0)); + ws = ws._1; + xs = xs._1; + ys = ys._1; + zs = zs._1; + } + return fromArray(arr); + } + + function map5(f, vs, ws, xs, ys, zs) + { + var arr = []; + while ( vs.ctor !== '[]' + && ws.ctor !== '[]' + && xs.ctor !== '[]' + && ys.ctor !== '[]' + && zs.ctor !== '[]') + { + arr.push(A5(f, vs._0, ws._0, xs._0, ys._0, zs._0)); + vs = vs._1; + ws = ws._1; + xs = xs._1; + ys = ys._1; + zs = zs._1; + } + return fromArray(arr); + } + + function sortBy(f, xs) + { + return fromArray(toArray(xs).sort(function(a,b){ + return Utils.cmp(f(a), f(b)); + })); + } + + function sortWith(f, xs) + { + return fromArray(toArray(xs).sort(function(a,b){ + var ord = f(a)(b).ctor; + return ord === 'EQ' ? 0 : ord === 'LT' ? -1 : 1; + })); + } + + function take(n, xs) + { + var arr = []; + while (xs.ctor !== '[]' && n > 0) + { + arr.push(xs._0); + xs = xs._1; + --n; + } + return fromArray(arr); + } + + function drop(n, xs) + { + while (xs.ctor !== '[]' && n > 0) + { + xs = xs._1; + --n; + } + return xs; + } + + function repeat(n, x) + { + var arr = []; + var pattern = [x]; + while (n > 0) + { + if (n & 1) + { + arr = arr.concat(pattern); + } + n >>= 1, pattern = pattern.concat(pattern); + } + return fromArray(arr); + } + + + Elm.Native.List.values = { + Nil:Nil, + Cons:Cons, + cons:F2(Cons), + toArray:toArray, + fromArray:fromArray, + range:range, + + foldl:F3(foldl), + foldr:F3(foldr), + + any:F2(any), + map2:F3(map2), + map3:F4(map3), + map4:F5(map4), + map5:F6(map5), + sortBy:F2(sortBy), + sortWith:F2(sortWith), + take:F2(take), + drop:F2(drop), + repeat:F2(repeat) + }; + return localRuntime.Native.List.values = Elm.Native.List.values; + +}; + + +// setup +Elm.Native = Elm.Native || {}; +Elm.Native.Markdown = Elm.Native.Markdown || {}; + +// definition +Elm.Native.Markdown.make = function(localRuntime) { + 'use strict'; + + // attempt to short-circuit + if ('values' in Elm.Native.Markdown) + { + return Elm.Native.Markdown.values; + } + + var Element = Elm.Native.Graphics.Element.make(localRuntime); + + /** + * marked - a markdown parser + * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) + * https://github.com/chjj/marked + */ + (function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,def:/^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+block.def.source+")")();block.blockquote=replace(block.blockquote)("def",block.def)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";block.html=replace(block.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,paragraph:/^/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1","\\2")+"|"+block.list.source.replace("\\1","\\3")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:"space"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i ?/gm,"");this.token(cap,top,true);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i1&&b.length>1)){src=cap.slice(i+1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false,bq);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:cap[1]==="pre"||cap[1]==="script"||cap[1]==="style",text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error("Tokens array requires a `links` property.")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1].charAt(6)===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=escape(this.smartypants(cap[0]));continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!=="!"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/--/g,"—").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…")};InlineLexer.prototype.mangle=function(text){var out="",l=text.length,i=0,ch;for(;i.5){ch="x"+ch.toString(16)}out+="&#"+ch+";"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return"
"+(escaped?code:escape(code,true))+"\n
"}return'
'+(escaped?code:escape(code,true))+"\n
\n"};Renderer.prototype.blockquote=function(quote){return"
\n"+quote+"
\n"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return"'+text+"\n"};Renderer.prototype.hr=function(){return this.options.xhtml?"
\n":"
\n"};Renderer.prototype.list=function(body,ordered){var type=ordered?"ol":"ul";return"<"+type+">\n"+body+"\n"};Renderer.prototype.listitem=function(text){return"
  • "+text+"
  • \n"};Renderer.prototype.paragraph=function(text){return"

    "+text+"

    \n"};Renderer.prototype.table=function(header,body){return"\n"+"\n"+header+"\n"+"\n"+body+"\n"+"
    \n"};Renderer.prototype.tablerow=function(content){return"\n"+content+"\n"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?"th":"td";var tag=flags.align?"<"+type+' style="text-align:'+flags.align+'">':"<"+type+">";return tag+content+"\n"};Renderer.prototype.strong=function(text){return""+text+""};Renderer.prototype.em=function(text){return""+text+""};Renderer.prototype.codespan=function(text){return""+text+""};Renderer.prototype.br=function(){return this.options.xhtml?"
    ":"
    "};Renderer.prototype.del=function(text){return""+text+""};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(prot.indexOf("javascript:")===0){return""}}var out='
    ";return out};Renderer.prototype.image=function(href,title,text){var out=''+text+'":">";return out};function Parser(options){this.tokens=[];this.token=null;this.options=options||marked.defaults;this.options.renderer=this.options.renderer||new Renderer;this.renderer=this.options.renderer;this.renderer.options=this.options}Parser.parse=function(src,options,renderer){var parser=new Parser(options,renderer);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options,this.renderer);this.tokens=src.reverse();var out="";while(this.next()){out+=this.tok()}return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;while(this.peek().type==="text"){body+="\n"+this.next().text}return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case"space":{return""}case"hr":{return this.renderer.hr()}case"heading":{return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text)}case"code":{return this.renderer.code(this.token.text,this.token.lang,this.token.escaped)}case"table":{var header="",body="",i,row,cell,flags,j;cell="";for(i=0;i/g,">").replace(/"/g,""").replace(/'/g,"'")}function unescape(html){return html.replace(/&([#\w]+);/g,function(_,n){n=n.toLowerCase();if(n==="colon")return":";if(n.charAt(0)==="#"){return n.charAt(1)==="x"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return""})}function replace(regex,opt){regex=regex.source;opt=opt||"";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\[])\^/g,"$1");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;iAn error occured:

    "+escape(e.message+"",true)+"
    "}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!=="undefined"&&typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}()); + + marked.setOptions({ + highlight: function (code, lang) { + if (typeof hljs !== 'undefined' + && lang + && hljs.listLanguages().indexOf(lang) >= 0) + { + return hljs.highlight(lang, code, true).value; + } + return code; + } + }); + + function formatOptions(options) { + var gfm = options.githubFlavored; + if (gfm.ctor === 'Just') + { + return { + gfm: true, + tables: gfm.tables, + breaks: gfm.breaks, + sanitize: options.sanitize, + smartypants: options.smartypants + }; + } + else + { + return { + gfm: false, + tables: false, + breaks: false, + sanitize: options.sanitize, + smartypants: options.smartypants + }; + } + } + + function toHtmlWith(options, rawMarkdown) { + var widget = { + type: "Widget", + + init: function () { + var html = marked(rawMarkdown, formatOptions(options)); + var div = document.createElement('div'); + div.innerHTML = html; + return div; + }, + + update: function (previous, node) { + var html = marked(rawMarkdown, formatOptions(options)); + node.innerHTML = html; + return node; + } + }; + return widget; + } + + function toElementWith(options, rawMarkdown) { + return Element.markdown(marked(rawMarkdown, formatOptions(options))); + } + + return Elm.Native.Markdown.values = { + toHtmlWith: F2(toHtmlWith), + toElementWith: F2(toElementWith) + }; +}; +Elm.Native.Port = {}; +Elm.Native.Port.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Port = localRuntime.Native.Port || {}; + if (localRuntime.Native.Port.values) + { + return localRuntime.Native.Port.values; + } + + var NS; + var Utils = Elm.Native.Utils.make(localRuntime); + + + // INBOUND + + function inbound(name, type, converter) + { + if (!localRuntime.argsTracker[name]) + { + throw new Error( + "Port Error:\n" + + "No argument was given for the port named '" + name + "' with type:\n\n" + + " " + type.split('\n').join('\n ') + "\n\n" + + "You need to provide an initial value!\n\n" + + "Find out more about ports here " + ); + } + var arg = localRuntime.argsTracker[name]; + arg.used = true; + + return jsToElm(name, type, converter, arg.value); + } + + + function inboundSignal(name, type, converter) + { + var initialValue = inbound(name, type, converter); + + if (!NS) + { + NS = Elm.Native.Signal.make(localRuntime); + } + var signal = NS.input('inbound-port-' + name, initialValue); + + function send(jsValue) + { + var elmValue = jsToElm(name, type, converter, jsValue); + setTimeout(function() { + localRuntime.notify(signal.id, elmValue); + }, 0); + } + + localRuntime.ports[name] = { send: send }; + + return signal; + } + + + function jsToElm(name, type, converter, value) + { + try + { + return converter(value); + } + catch(e) + { + throw new Error( + "Port Error:\n" + + "Regarding the port named '" + name + "' with type:\n\n" + + " " + type.split('\n').join('\n ') + "\n\n" + + "You just sent the value:\n\n" + + " " + JSON.stringify(arg.value) + "\n\n" + + "but it cannot be converted to the necessary type.\n" + + e.message + ); + } + } + + + // OUTBOUND + + function outbound(name, converter, elmValue) + { + localRuntime.ports[name] = converter(elmValue); + } + + + function outboundSignal(name, converter, signal) + { + var subscribers = []; + + function subscribe(handler) + { + subscribers.push(handler); + } + function unsubscribe(handler) + { + subscribers.pop(subscribers.indexOf(handler)); + } + + function notify(elmValue) + { + var jsValue = converter(elmValue); + var len = subscribers.length; + for (var i = 0; i < len; ++i) + { + subscribers[i](jsValue); + } + } + + if (!NS) + { + NS = Elm.Native.Signal.make(localRuntime); + } + NS.output('outbound-port-' + name, notify, signal); + + localRuntime.ports[name] = { + subscribe: subscribe, + unsubscribe: unsubscribe + }; + + return signal; + } + + + return localRuntime.Native.Port.values = { + inbound: inbound, + outbound: outbound, + inboundSignal: inboundSignal, + outboundSignal: outboundSignal + }; +}; + + +if (!Elm.fullscreen) { + + (function() { + 'use strict'; + + var Display = { + FULLSCREEN: 0, + COMPONENT: 1, + NONE: 2 + }; + + Elm.fullscreen = function(module, args) + { + var container = document.createElement('div'); + document.body.appendChild(container); + return init(Display.FULLSCREEN, container, module, args || {}); + }; + + Elm.embed = function(module, container, args) + { + var tag = container.tagName; + if (tag !== 'DIV') + { + throw new Error('Elm.node must be given a DIV, not a ' + tag + '.'); + } + return init(Display.COMPONENT, container, module, args || {}); + }; + + Elm.worker = function(module, args) + { + return init(Display.NONE, {}, module, args || {}); + }; + + function init(display, container, module, args, moduleToReplace) + { + // defining state needed for an instance of the Elm RTS + var inputs = []; + + /* OFFSET + * Elm's time traveling debugger lets you pause time. This means + * "now" may be shifted a bit into the past. By wrapping Date.now() + * we can manage this. + */ + var timer = { + programStart: Date.now(), + now: function() + { + return Date.now(); + } + }; + + var updateInProgress = false; + function notify(id, v) + { + if (updateInProgress) + { + throw new Error( + 'The notify function has been called synchronously!\n' + + 'This can lead to frames being dropped.\n' + + 'Definitely report this to \n'); + } + updateInProgress = true; + var timestep = timer.now(); + for (var i = inputs.length; i--; ) + { + inputs[i].notify(timestep, id, v); + } + updateInProgress = false; + } + function setTimeout(func, delay) + { + return window.setTimeout(func, delay); + } + + var listeners = []; + function addListener(relevantInputs, domNode, eventName, func) + { + domNode.addEventListener(eventName, func); + var listener = { + relevantInputs: relevantInputs, + domNode: domNode, + eventName: eventName, + func: func + }; + listeners.push(listener); + } + + var argsTracker = {}; + for (var name in args) + { + argsTracker[name] = { + value: args[name], + used: false + }; + } + + // create the actual RTS. Any impure modules will attach themselves to this + // object. This permits many Elm programs to be embedded per document. + var elm = { + notify: notify, + setTimeout: setTimeout, + node: container, + addListener: addListener, + inputs: inputs, + timer: timer, + argsTracker: argsTracker, + ports: {}, + + isFullscreen: function() { return display === Display.FULLSCREEN; }, + isEmbed: function() { return display === Display.COMPONENT; }, + isWorker: function() { return display === Display.NONE; } + }; + + function swap(newModule) + { + removeListeners(listeners); + var div = document.createElement('div'); + var newElm = init(display, div, newModule, args, elm); + inputs = []; + // elm.swap = newElm.swap; + return newElm; + } + + function dispose() + { + removeListeners(listeners); + inputs = []; + } + + var Module = {}; + try + { + Module = module.make(elm); + checkInputs(elm); + } + catch (error) + { + if (typeof container.appendChild == 'undefined') + { + console.log(error.message); + } + else + { + container.appendChild(errorNode(error.message)); + } + throw error; + } + + if (display !== Display.NONE) + { + var graphicsNode = initGraphics(elm, Module); + } + + var rootNode = { kids: inputs }; + trimDeadNodes(rootNode); + inputs = rootNode.kids; + filterListeners(inputs, listeners); + + addReceivers(elm.ports); + + if (typeof moduleToReplace !== 'undefined') + { + hotSwap(moduleToReplace, elm); + + // rerender scene if graphics are enabled. + if (typeof graphicsNode !== 'undefined') + { + graphicsNode.notify(0, true, 0); + } + } + + return { + swap: swap, + ports: elm.ports, + dispose: dispose + }; + }; + + function checkInputs(elm) + { + var argsTracker = elm.argsTracker; + for (var name in argsTracker) + { + if (!argsTracker[name].used) + { + throw new Error( + "Port Error:\nYou provided an argument named '" + name + + "' but there is no corresponding port!\n\n" + + "Maybe add a port '" + name + "' to your Elm module?\n" + + "Maybe remove the '" + name + "' argument from your initialization code in JS?" + ); + } + } + } + + function errorNode(message) + { + var code = document.createElement('code'); + + var lines = message.split('\n'); + code.appendChild(document.createTextNode(lines[0])); + code.appendChild(document.createElement('br')); + code.appendChild(document.createElement('br')); + for (var i = 1; i < lines.length; ++i) + { + code.appendChild(document.createTextNode('\u00A0 \u00A0 ' + lines[i].replace(/ /g, '\u00A0 '))); + code.appendChild(document.createElement('br')); + } + code.appendChild(document.createElement('br')); + code.appendChild(document.createTextNode("Open the developer console for more details.")); + return code; + } + + + //// FILTER SIGNALS //// + + // TODO: move this code into the signal module and create a function + // Signal.initializeGraph that actually instantiates everything. + + function filterListeners(inputs, listeners) + { + loop: + for (var i = listeners.length; i--; ) + { + var listener = listeners[i]; + for (var j = inputs.length; j--; ) + { + if (listener.relevantInputs.indexOf(inputs[j].id) >= 0) + { + continue loop; + } + } + listener.domNode.removeEventListener(listener.eventName, listener.func); + } + } + + function removeListeners(listeners) + { + for (var i = listeners.length; i--; ) + { + var listener = listeners[i]; + listener.domNode.removeEventListener(listener.eventName, listener.func); + } + } + + // add receivers for built-in ports if they are defined + function addReceivers(ports) + { + if ('title' in ports) + { + if (typeof ports.title === 'string') + { + document.title = ports.title; + } + else + { + ports.title.subscribe(function(v) { document.title = v; }); + } + } + if ('redirect' in ports) + { + ports.redirect.subscribe(function(v) { + if (v.length > 0) + { + window.location = v; + } + }); + } + } + + + // returns a boolean representing whether the node is alive or not. + function trimDeadNodes(node) + { + if (node.isOutput) + { + return true; + } + + var liveKids = []; + for (var i = node.kids.length; i--; ) + { + var kid = node.kids[i]; + if (trimDeadNodes(kid)) + { + liveKids.push(kid); + } + } + node.kids = liveKids; + + return liveKids.length > 0; + } + + + //// RENDERING //// + + function initGraphics(elm, Module) + { + if (!('main' in Module)) + { + throw new Error("'main' is missing! What do I display?!"); + } + + var signalGraph = Module.main; + + // make sure the signal graph is actually a signal & extract the visual model + if (!('notify' in signalGraph)) + { + signalGraph = Elm.Signal.make(elm).constant(signalGraph); + } + var initialScene = signalGraph.value; + + // Figure out what the render functions should be + var render; + var update; + if (initialScene.props) + { + var Element = Elm.Native.Graphics.Element.make(elm); + render = Element.render; + update = Element.updateAndReplace; + } + else + { + var VirtualDom = Elm.Native.VirtualDom.make(elm); + render = VirtualDom.render; + update = VirtualDom.updateAndReplace; + } + + // Add the initialScene to the DOM + var container = elm.node; + var node = render(initialScene); + while (container.firstChild) + { + container.removeChild(container.firstChild); + } + container.appendChild(node); + + var _requestAnimationFrame = + typeof requestAnimationFrame !== 'undefined' + ? requestAnimationFrame + : function(cb) { setTimeout(cb, 1000/60); } + ; + + // domUpdate is called whenever the main Signal changes. + // + // domUpdate and drawCallback implement a small state machine in order + // to schedule only 1 draw per animation frame. This enforces that + // once draw has been called, it will not be called again until the + // next frame. + // + // drawCallback is scheduled whenever + // 1. The state transitions from PENDING_REQUEST to EXTRA_REQUEST, or + // 2. The state transitions from NO_REQUEST to PENDING_REQUEST + // + // Invariants: + // 1. In the NO_REQUEST state, there is never a scheduled drawCallback. + // 2. In the PENDING_REQUEST and EXTRA_REQUEST states, there is always exactly 1 + // scheduled drawCallback. + var NO_REQUEST = 0; + var PENDING_REQUEST = 1; + var EXTRA_REQUEST = 2; + var state = NO_REQUEST; + var savedScene = initialScene; + var scheduledScene = initialScene; + + function domUpdate(newScene) + { + scheduledScene = newScene; + + switch (state) + { + case NO_REQUEST: + _requestAnimationFrame(drawCallback); + state = PENDING_REQUEST; + return; + case PENDING_REQUEST: + state = PENDING_REQUEST; + return; + case EXTRA_REQUEST: + state = PENDING_REQUEST; + return; + } + } + + function drawCallback() + { + switch (state) + { + case NO_REQUEST: + // This state should not be possible. How can there be no + // request, yet somehow we are actively fulfilling a + // request? + throw new Error( + "Unexpected draw callback.\n" + + "Please report this to ." + ); + + case PENDING_REQUEST: + // At this point, we do not *know* that another frame is + // needed, but we make an extra request to rAF just in + // case. It's possible to drop a frame if rAF is called + // too late, so we just do it preemptively. + _requestAnimationFrame(drawCallback); + state = EXTRA_REQUEST; + + // There's also stuff we definitely need to draw. + draw(); + return; + + case EXTRA_REQUEST: + // Turns out the extra request was not needed, so we will + // stop calling rAF. No reason to call it all the time if + // no one needs it. + state = NO_REQUEST; + return; + } + } + + function draw() + { + update(elm.node.firstChild, savedScene, scheduledScene); + if (elm.Native.Window) + { + elm.Native.Window.values.resizeIfNeeded(); + } + savedScene = scheduledScene; + } + + var renderer = Elm.Native.Signal.make(elm).output('main', domUpdate, signalGraph); + + // must check for resize after 'renderer' is created so + // that changes show up. + if (elm.Native.Window) + { + elm.Native.Window.values.resizeIfNeeded(); + } + + return renderer; + } + + //// HOT SWAPPING //// + + // Returns boolean indicating if the swap was successful. + // Requires that the two signal graphs have exactly the same + // structure. + function hotSwap(from, to) + { + function similar(nodeOld,nodeNew) + { + if (nodeOld.id !== nodeNew.id) + { + return false; + } + if (nodeOld.isOutput) + { + return nodeNew.isOutput; + } + return nodeOld.kids.length === nodeNew.kids.length; + } + function swap(nodeOld,nodeNew) + { + nodeNew.value = nodeOld.value; + return true; + } + var canSwap = depthFirstTraversals(similar, from.inputs, to.inputs); + if (canSwap) + { + depthFirstTraversals(swap, from.inputs, to.inputs); + } + from.node.parentNode.replaceChild(to.node, from.node); + + return canSwap; + } + + // Returns false if the node operation f ever fails. + function depthFirstTraversals(f, queueOld, queueNew) + { + if (queueOld.length !== queueNew.length) + { + return false; + } + queueOld = queueOld.slice(0); + queueNew = queueNew.slice(0); + + var seen = []; + while (queueOld.length > 0 && queueNew.length > 0) + { + var nodeOld = queueOld.pop(); + var nodeNew = queueNew.pop(); + if (seen.indexOf(nodeOld.id) < 0) + { + if (!f(nodeOld, nodeNew)) + { + return false; + } + queueOld = queueOld.concat(nodeOld.kids || []); + queueNew = queueNew.concat(nodeNew.kids || []); + seen.push(nodeOld.id); + } + } + return true; + } + }()); + + function F2(fun) + { + function wrapper(a) { return function(b) { return fun(a,b) } } + wrapper.arity = 2; + wrapper.func = fun; + return wrapper; + } + + function F3(fun) + { + function wrapper(a) { + return function(b) { return function(c) { return fun(a,b,c) }} + } + wrapper.arity = 3; + wrapper.func = fun; + return wrapper; + } + + function F4(fun) + { + function wrapper(a) { return function(b) { return function(c) { + return function(d) { return fun(a,b,c,d) }}} + } + wrapper.arity = 4; + wrapper.func = fun; + return wrapper; + } + + function F5(fun) + { + function wrapper(a) { return function(b) { return function(c) { + return function(d) { return function(e) { return fun(a,b,c,d,e) }}}} + } + wrapper.arity = 5; + wrapper.func = fun; + return wrapper; + } + + function F6(fun) + { + function wrapper(a) { return function(b) { return function(c) { + return function(d) { return function(e) { return function(f) { + return fun(a,b,c,d,e,f) }}}}} + } + wrapper.arity = 6; + wrapper.func = fun; + return wrapper; + } + + function F7(fun) + { + function wrapper(a) { return function(b) { return function(c) { + return function(d) { return function(e) { return function(f) { + return function(g) { return fun(a,b,c,d,e,f,g) }}}}}} + } + wrapper.arity = 7; + wrapper.func = fun; + return wrapper; + } + + function F8(fun) + { + function wrapper(a) { return function(b) { return function(c) { + return function(d) { return function(e) { return function(f) { + return function(g) { return function(h) { + return fun(a,b,c,d,e,f,g,h)}}}}}}} + } + wrapper.arity = 8; + wrapper.func = fun; + return wrapper; + } + + function F9(fun) + { + function wrapper(a) { return function(b) { return function(c) { + return function(d) { return function(e) { return function(f) { + return function(g) { return function(h) { return function(i) { + return fun(a,b,c,d,e,f,g,h,i) }}}}}}}} + } + wrapper.arity = 9; + wrapper.func = fun; + return wrapper; + } + + function A2(fun,a,b) + { + return fun.arity === 2 + ? fun.func(a,b) + : fun(a)(b); + } + function A3(fun,a,b,c) + { + return fun.arity === 3 + ? fun.func(a,b,c) + : fun(a)(b)(c); + } + function A4(fun,a,b,c,d) + { + return fun.arity === 4 + ? fun.func(a,b,c,d) + : fun(a)(b)(c)(d); + } + function A5(fun,a,b,c,d,e) + { + return fun.arity === 5 + ? fun.func(a,b,c,d,e) + : fun(a)(b)(c)(d)(e); + } + function A6(fun,a,b,c,d,e,f) + { + return fun.arity === 6 + ? fun.func(a,b,c,d,e,f) + : fun(a)(b)(c)(d)(e)(f); + } + function A7(fun,a,b,c,d,e,f,g) + { + return fun.arity === 7 + ? fun.func(a,b,c,d,e,f,g) + : fun(a)(b)(c)(d)(e)(f)(g); + } + function A8(fun,a,b,c,d,e,f,g,h) + { + return fun.arity === 8 + ? fun.func(a,b,c,d,e,f,g,h) + : fun(a)(b)(c)(d)(e)(f)(g)(h); + } + function A9(fun,a,b,c,d,e,f,g,h,i) + { + return fun.arity === 9 + ? fun.func(a,b,c,d,e,f,g,h,i) + : fun(a)(b)(c)(d)(e)(f)(g)(h)(i); + } +} + +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o + * @license MIT + */ + +var base64 = require('base64-js') +var ieee754 = require('ieee754') +var isArray = require('is-array') + +exports.Buffer = Buffer +exports.SlowBuffer = SlowBuffer +exports.INSPECT_MAX_BYTES = 50 +Buffer.poolSize = 8192 // not used by this implementation + +var kMaxLength = 0x3fffffff +var rootParent = {} + +/** + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Use Object implementation (most compatible, even IE6) + * + * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, + * Opera 11.6+, iOS 4.2+. + * + * Note: + * + * - Implementation must support adding new properties to `Uint8Array` instances. + * Firefox 4-29 lacked support, fixed in Firefox 30+. + * See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438. + * + * - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function. + * + * - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of + * incorrect length in some situations. + * + * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they will + * get the Object implementation, which is slower but will work correctly. + */ +Buffer.TYPED_ARRAY_SUPPORT = (function () { + try { + var buf = new ArrayBuffer(0) + var arr = new Uint8Array(buf) + arr.foo = function () { return 42 } + return arr.foo() === 42 && // typed array instances can be augmented + typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray` + new Uint8Array(1).subarray(1, 1).byteLength === 0 // ie10 has broken `subarray` + } catch (e) { + return false + } +})() + +/** + * Class: Buffer + * ============= + * + * The Buffer constructor returns instances of `Uint8Array` that are augmented + * with function properties for all the node `Buffer` API functions. We use + * `Uint8Array` so that square bracket notation works as expected -- it returns + * a single octet. + * + * By augmenting the instances, we can avoid modifying the `Uint8Array` + * prototype. + */ +function Buffer (arg) { + if (!(this instanceof Buffer)) { + // Avoid going through an ArgumentsAdaptorTrampoline in the common case. + if (arguments.length > 1) return new Buffer(arg, arguments[1]) + return new Buffer(arg) + } + + this.length = 0 + this.parent = undefined + + // Common case. + if (typeof arg === 'number') { + return fromNumber(this, arg) + } + + // Slightly less common case. + if (typeof arg === 'string') { + return fromString(this, arg, arguments.length > 1 ? arguments[1] : 'utf8') + } + + // Unusual. + return fromObject(this, arg) +} + +function fromNumber (that, length) { + that = allocate(that, length < 0 ? 0 : checked(length) | 0) + if (!Buffer.TYPED_ARRAY_SUPPORT) { + for (var i = 0; i < length; i++) { + that[i] = 0 + } + } + return that +} + +function fromString (that, string, encoding) { + if (typeof encoding !== 'string' || encoding === '') encoding = 'utf8' + + // Assumption: byteLength() return value is always < kMaxLength. + var length = byteLength(string, encoding) | 0 + that = allocate(that, length) + + that.write(string, encoding) + return that +} + +function fromObject (that, object) { + if (Buffer.isBuffer(object)) return fromBuffer(that, object) + + if (isArray(object)) return fromArray(that, object) + + if (object == null) { + throw new TypeError('must start with number, buffer, array or string') + } + + if (typeof ArrayBuffer !== 'undefined' && object.buffer instanceof ArrayBuffer) { + return fromTypedArray(that, object) + } + + if (object.length) return fromArrayLike(that, object) + + return fromJsonObject(that, object) +} + +function fromBuffer (that, buffer) { + var length = checked(buffer.length) | 0 + that = allocate(that, length) + buffer.copy(that, 0, 0, length) + return that +} + +function fromArray (that, array) { + var length = checked(array.length) | 0 + that = allocate(that, length) + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that +} + +// Duplicate of fromArray() to keep fromArray() monomorphic. +function fromTypedArray (that, array) { + var length = checked(array.length) | 0 + that = allocate(that, length) + // Truncating the elements is probably not what people expect from typed + // arrays with BYTES_PER_ELEMENT > 1 but it's compatible with the behavior + // of the old Buffer constructor. + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that +} + +function fromArrayLike (that, array) { + var length = checked(array.length) | 0 + that = allocate(that, length) + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that +} + +// Deserialize { type: 'Buffer', data: [1,2,3,...] } into a Buffer object. +// Returns a zero-length buffer for inputs that don't conform to the spec. +function fromJsonObject (that, object) { + var array + var length = 0 + + if (object.type === 'Buffer' && isArray(object.data)) { + array = object.data + length = checked(array.length) | 0 + } + that = allocate(that, length) + + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that +} + +function allocate (that, length) { + if (Buffer.TYPED_ARRAY_SUPPORT) { + // Return an augmented `Uint8Array` instance, for best performance + that = Buffer._augment(new Uint8Array(length)) + } else { + // Fallback: Return an object instance of the Buffer class + that.length = length + that._isBuffer = true + } + + var fromPool = length !== 0 && length <= Buffer.poolSize >>> 1 + if (fromPool) that.parent = rootParent + + return that +} + +function checked (length) { + // Note: cannot use `length < kMaxLength` here because that fails when + // length is NaN (which is otherwise coerced to zero.) + if (length >= kMaxLength) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + + 'size: 0x' + kMaxLength.toString(16) + ' bytes') + } + return length | 0 +} + +function SlowBuffer (subject, encoding) { + if (!(this instanceof SlowBuffer)) return new SlowBuffer(subject, encoding) + + var buf = new Buffer(subject, encoding) + delete buf.parent + return buf +} + +Buffer.isBuffer = function isBuffer (b) { + return !!(b != null && b._isBuffer) +} + +Buffer.compare = function compare (a, b) { + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError('Arguments must be Buffers') + } + + if (a === b) return 0 + + var x = a.length + var y = b.length + + var i = 0 + var len = Math.min(x, y) + while (i < len) { + if (a[i] !== b[i]) break + + ++i + } + + if (i !== len) { + x = a[i] + y = b[i] + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +Buffer.isEncoding = function isEncoding (encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'binary': + case 'base64': + case 'raw': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true + default: + return false + } +} + +Buffer.concat = function concat (list, length) { + if (!isArray(list)) throw new TypeError('list argument must be an Array of Buffers.') + + if (list.length === 0) { + return new Buffer(0) + } else if (list.length === 1) { + return list[0] + } + + var i + if (length === undefined) { + length = 0 + for (i = 0; i < list.length; i++) { + length += list[i].length + } + } + + var buf = new Buffer(length) + var pos = 0 + for (i = 0; i < list.length; i++) { + var item = list[i] + item.copy(buf, pos) + pos += item.length + } + return buf +} + +function byteLength (string, encoding) { + if (typeof string !== 'string') string = String(string) + + if (string.length === 0) return 0 + + switch (encoding || 'utf8') { + case 'ascii': + case 'binary': + case 'raw': + return string.length + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return string.length * 2 + case 'hex': + return string.length >>> 1 + case 'utf8': + case 'utf-8': + return utf8ToBytes(string).length + case 'base64': + return base64ToBytes(string).length + default: + return string.length + } +} +Buffer.byteLength = byteLength + +// pre-set for values that may exist in the future +Buffer.prototype.length = undefined +Buffer.prototype.parent = undefined + +// toString(encoding, start=0, end=buffer.length) +Buffer.prototype.toString = function toString (encoding, start, end) { + var loweredCase = false + + start = start | 0 + end = end === undefined || end === Infinity ? this.length : end | 0 + + if (!encoding) encoding = 'utf8' + if (start < 0) start = 0 + if (end > this.length) end = this.length + if (end <= start) return '' + + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end) + + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end) + + case 'ascii': + return asciiSlice(this, start, end) + + case 'binary': + return binarySlice(this, start, end) + + case 'base64': + return base64Slice(this, start, end) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, start, end) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = (encoding + '').toLowerCase() + loweredCase = true + } + } +} + +Buffer.prototype.equals = function equals (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return true + return Buffer.compare(this, b) === 0 +} + +Buffer.prototype.inspect = function inspect () { + var str = '' + var max = exports.INSPECT_MAX_BYTES + if (this.length > 0) { + str = this.toString('hex', 0, max).match(/.{2}/g).join(' ') + if (this.length > max) str += ' ... ' + } + return '' +} + +Buffer.prototype.compare = function compare (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return 0 + return Buffer.compare(this, b) +} + +Buffer.prototype.indexOf = function indexOf (val, byteOffset) { + if (byteOffset > 0x7fffffff) byteOffset = 0x7fffffff + else if (byteOffset < -0x80000000) byteOffset = -0x80000000 + byteOffset >>= 0 + + if (this.length === 0) return -1 + if (byteOffset >= this.length) return -1 + + // Negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = Math.max(this.length + byteOffset, 0) + + if (typeof val === 'string') { + if (val.length === 0) return -1 // special case: looking for empty string always fails + return String.prototype.indexOf.call(this, val, byteOffset) + } + if (Buffer.isBuffer(val)) { + return arrayIndexOf(this, val, byteOffset) + } + if (typeof val === 'number') { + if (Buffer.TYPED_ARRAY_SUPPORT && Uint8Array.prototype.indexOf === 'function') { + return Uint8Array.prototype.indexOf.call(this, val, byteOffset) + } + return arrayIndexOf(this, [ val ], byteOffset) + } + + function arrayIndexOf (arr, val, byteOffset) { + var foundIndex = -1 + for (var i = 0; byteOffset + i < arr.length; i++) { + if (arr[byteOffset + i] === val[foundIndex === -1 ? 0 : i - foundIndex]) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === val.length) return byteOffset + foundIndex + } else { + foundIndex = -1 + } + } + return -1 + } + + throw new TypeError('val must be string, number or Buffer') +} + +// `get` will be removed in Node 0.13+ +Buffer.prototype.get = function get (offset) { + console.log('.get() is deprecated. Access using array indexes instead.') + return this.readUInt8(offset) +} + +// `set` will be removed in Node 0.13+ +Buffer.prototype.set = function set (v, offset) { + console.log('.set() is deprecated. Access using array indexes instead.') + return this.writeUInt8(v, offset) +} + +function hexWrite (buf, string, offset, length) { + offset = Number(offset) || 0 + var remaining = buf.length - offset + if (!length) { + length = remaining + } else { + length = Number(length) + if (length > remaining) { + length = remaining + } + } + + // must be an even number of digits + var strLen = string.length + if (strLen % 2 !== 0) throw new Error('Invalid hex string') + + if (length > strLen / 2) { + length = strLen / 2 + } + for (var i = 0; i < length; i++) { + var parsed = parseInt(string.substr(i * 2, 2), 16) + if (isNaN(parsed)) throw new Error('Invalid hex string') + buf[offset + i] = parsed + } + return i +} + +function utf8Write (buf, string, offset, length) { + return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) +} + +function asciiWrite (buf, string, offset, length) { + return blitBuffer(asciiToBytes(string), buf, offset, length) +} + +function binaryWrite (buf, string, offset, length) { + return asciiWrite(buf, string, offset, length) +} + +function base64Write (buf, string, offset, length) { + return blitBuffer(base64ToBytes(string), buf, offset, length) +} + +function ucs2Write (buf, string, offset, length) { + return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) +} + +Buffer.prototype.write = function write (string, offset, length, encoding) { + // Buffer#write(string) + if (offset === undefined) { + encoding = 'utf8' + length = this.length + offset = 0 + // Buffer#write(string, encoding) + } else if (length === undefined && typeof offset === 'string') { + encoding = offset + length = this.length + offset = 0 + // Buffer#write(string, offset[, length][, encoding]) + } else if (isFinite(offset)) { + offset = offset | 0 + if (isFinite(length)) { + length = length | 0 + if (encoding === undefined) encoding = 'utf8' + } else { + encoding = length + length = undefined + } + // legacy write(string, encoding, offset, length) - remove in v0.13 + } else { + var swap = encoding + encoding = offset + offset = length | 0 + length = swap + } + + var remaining = this.length - offset + if (length === undefined || length > remaining) length = remaining + + if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { + throw new RangeError('attempt to write outside buffer bounds') + } + + if (!encoding) encoding = 'utf8' + + var loweredCase = false + for (;;) { + switch (encoding) { + case 'hex': + return hexWrite(this, string, offset, length) + + case 'utf8': + case 'utf-8': + return utf8Write(this, string, offset, length) + + case 'ascii': + return asciiWrite(this, string, offset, length) + + case 'binary': + return binaryWrite(this, string, offset, length) + + case 'base64': + // Warning: maxLength not taken into account in base64Write + return base64Write(this, string, offset, length) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, string, offset, length) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} + +Buffer.prototype.toJSON = function toJSON () { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0) + } +} + +function base64Slice (buf, start, end) { + if (start === 0 && end === buf.length) { + return base64.fromByteArray(buf) + } else { + return base64.fromByteArray(buf.slice(start, end)) + } +} + +function utf8Slice (buf, start, end) { + var res = '' + var tmp = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; i++) { + if (buf[i] <= 0x7F) { + res += decodeUtf8Char(tmp) + String.fromCharCode(buf[i]) + tmp = '' + } else { + tmp += '%' + buf[i].toString(16) + } + } + + return res + decodeUtf8Char(tmp) +} + +function asciiSlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; i++) { + ret += String.fromCharCode(buf[i] & 0x7F) + } + return ret +} + +function binarySlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; i++) { + ret += String.fromCharCode(buf[i]) + } + return ret +} + +function hexSlice (buf, start, end) { + var len = buf.length + + if (!start || start < 0) start = 0 + if (!end || end < 0 || end > len) end = len + + var out = '' + for (var i = start; i < end; i++) { + out += toHex(buf[i]) + } + return out +} + +function utf16leSlice (buf, start, end) { + var bytes = buf.slice(start, end) + var res = '' + for (var i = 0; i < bytes.length; i += 2) { + res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256) + } + return res +} + +Buffer.prototype.slice = function slice (start, end) { + var len = this.length + start = ~~start + end = end === undefined ? len : ~~end + + if (start < 0) { + start += len + if (start < 0) start = 0 + } else if (start > len) { + start = len + } + + if (end < 0) { + end += len + if (end < 0) end = 0 + } else if (end > len) { + end = len + } + + if (end < start) end = start + + var newBuf + if (Buffer.TYPED_ARRAY_SUPPORT) { + newBuf = Buffer._augment(this.subarray(start, end)) + } else { + var sliceLen = end - start + newBuf = new Buffer(sliceLen, undefined) + for (var i = 0; i < sliceLen; i++) { + newBuf[i] = this[i + start] + } + } + + if (newBuf.length) newBuf.parent = this.parent || this + + return newBuf +} + +/* + * Need to make sure that buffer isn't trying to write out of bounds. + */ +function checkOffset (offset, ext, length) { + if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint') + if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length') +} + +Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + + return val +} + +Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) { + checkOffset(offset, byteLength, this.length) + } + + var val = this[offset + --byteLength] + var mul = 1 + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul + } + + return val +} + +Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { + if (!noAssert) checkOffset(offset, 1, this.length) + return this[offset] +} + +Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + return this[offset] | (this[offset + 1] << 8) +} + +Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + return (this[offset] << 8) | this[offset + 1] +} + +Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return ((this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16)) + + (this[offset + 3] * 0x1000000) +} + +Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] * 0x1000000) + + ((this[offset + 1] << 16) | + (this[offset + 2] << 8) | + this[offset + 3]) +} + +Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var i = byteLength + var mul = 1 + var val = this[offset + --i] + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { + if (!noAssert) checkOffset(offset, 1, this.length) + if (!(this[offset] & 0x80)) return (this[offset]) + return ((0xff - this[offset] + 1) * -1) +} + +Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset] | (this[offset + 1] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset + 1] | (this[offset] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16) | + (this[offset + 3] << 24) +} + +Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] << 24) | + (this[offset + 1] << 16) | + (this[offset + 2] << 8) | + (this[offset + 3]) +} + +Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, true, 23, 4) +} + +Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, false, 23, 4) +} + +Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, true, 52, 8) +} + +Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, false, 52, 8) +} + +function checkInt (buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('buffer must be a Buffer instance') + if (value > max || value < min) throw new RangeError('value is out of bounds') + if (offset + ext > buf.length) throw new RangeError('index out of range') +} + +Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0) + + var mul = 1 + var i = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0) + + var i = byteLength - 1 + var mul = 1 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) + if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value) + this[offset] = value + return offset + 1 +} + +function objectWriteUInt16 (buf, value, offset, littleEndian) { + if (value < 0) value = 0xffff + value + 1 + for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; i++) { + buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>> + (littleEndian ? i : 1 - i) * 8 + } +} + +Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = value + this[offset + 1] = (value >>> 8) + } else { + objectWriteUInt16(this, value, offset, true) + } + return offset + 2 +} + +Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 8) + this[offset + 1] = value + } else { + objectWriteUInt16(this, value, offset, false) + } + return offset + 2 +} + +function objectWriteUInt32 (buf, value, offset, littleEndian) { + if (value < 0) value = 0xffffffff + value + 1 + for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; i++) { + buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff + } +} + +Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset + 3] = (value >>> 24) + this[offset + 2] = (value >>> 16) + this[offset + 1] = (value >>> 8) + this[offset] = value + } else { + objectWriteUInt32(this, value, offset, true) + } + return offset + 4 +} + +Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = value + } else { + objectWriteUInt32(this, value, offset, false) + } + return offset + 4 +} + +Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) { + var limit = Math.pow(2, 8 * byteLength - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = 0 + var mul = 1 + var sub = value < 0 ? 1 : 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) { + var limit = Math.pow(2, 8 * byteLength - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = byteLength - 1 + var mul = 1 + var sub = value < 0 ? 1 : 0 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) + if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value) + if (value < 0) value = 0xff + value + 1 + this[offset] = value + return offset + 1 +} + +Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = value + this[offset + 1] = (value >>> 8) + } else { + objectWriteUInt16(this, value, offset, true) + } + return offset + 2 +} + +Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 8) + this[offset + 1] = value + } else { + objectWriteUInt16(this, value, offset, false) + } + return offset + 2 +} + +Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = value + this[offset + 1] = (value >>> 8) + this[offset + 2] = (value >>> 16) + this[offset + 3] = (value >>> 24) + } else { + objectWriteUInt32(this, value, offset, true) + } + return offset + 4 +} + +Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (value < 0) value = 0xffffffff + value + 1 + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = value + } else { + objectWriteUInt32(this, value, offset, false) + } + return offset + 4 +} + +function checkIEEE754 (buf, value, offset, ext, max, min) { + if (value > max || value < min) throw new RangeError('value is out of bounds') + if (offset + ext > buf.length) throw new RangeError('index out of range') + if (offset < 0) throw new RangeError('index out of range') +} + +function writeFloat (buf, value, offset, littleEndian, noAssert) { + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) + } + ieee754.write(buf, value, offset, littleEndian, 23, 4) + return offset + 4 +} + +Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert) +} + +function writeDouble (buf, value, offset, littleEndian, noAssert) { + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) + } + ieee754.write(buf, value, offset, littleEndian, 52, 8) + return offset + 8 +} + +Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert) +} + +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function copy (target, targetStart, start, end) { + if (!start) start = 0 + if (!end && end !== 0) end = this.length + if (targetStart >= target.length) targetStart = target.length + if (!targetStart) targetStart = 0 + if (end > 0 && end < start) end = start + + // Copy 0 bytes; we're done + if (end === start) return 0 + if (target.length === 0 || this.length === 0) return 0 + + // Fatal error conditions + if (targetStart < 0) { + throw new RangeError('targetStart out of bounds') + } + if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds') + if (end < 0) throw new RangeError('sourceEnd out of bounds') + + // Are we oob? + if (end > this.length) end = this.length + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start + } + + var len = end - start + + if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) { + for (var i = 0; i < len; i++) { + target[i + targetStart] = this[i + start] + } + } else { + target._set(this.subarray(start, start + len), targetStart) + } + + return len +} + +// fill(value, start=0, end=buffer.length) +Buffer.prototype.fill = function fill (value, start, end) { + if (!value) value = 0 + if (!start) start = 0 + if (!end) end = this.length + + if (end < start) throw new RangeError('end < start') + + // Fill 0 bytes; we're done + if (end === start) return + if (this.length === 0) return + + if (start < 0 || start >= this.length) throw new RangeError('start out of bounds') + if (end < 0 || end > this.length) throw new RangeError('end out of bounds') + + var i + if (typeof value === 'number') { + for (i = start; i < end; i++) { + this[i] = value + } + } else { + var bytes = utf8ToBytes(value.toString()) + var len = bytes.length + for (i = start; i < end; i++) { + this[i] = bytes[i % len] + } + } + + return this +} + +/** + * Creates a new `ArrayBuffer` with the *copied* memory of the buffer instance. + * Added in Node 0.12. Only available in browsers that support ArrayBuffer. + */ +Buffer.prototype.toArrayBuffer = function toArrayBuffer () { + if (typeof Uint8Array !== 'undefined') { + if (Buffer.TYPED_ARRAY_SUPPORT) { + return (new Buffer(this)).buffer + } else { + var buf = new Uint8Array(this.length) + for (var i = 0, len = buf.length; i < len; i += 1) { + buf[i] = this[i] + } + return buf.buffer + } + } else { + throw new TypeError('Buffer.toArrayBuffer not supported in this browser') + } +} + +// HELPER FUNCTIONS +// ================ + +var BP = Buffer.prototype + +/** + * Augment a Uint8Array *instance* (not the Uint8Array class!) with Buffer methods + */ +Buffer._augment = function _augment (arr) { + arr.constructor = Buffer + arr._isBuffer = true + + // save reference to original Uint8Array set method before overwriting + arr._set = arr.set + + // deprecated, will be removed in node 0.13+ + arr.get = BP.get + arr.set = BP.set + + arr.write = BP.write + arr.toString = BP.toString + arr.toLocaleString = BP.toString + arr.toJSON = BP.toJSON + arr.equals = BP.equals + arr.compare = BP.compare + arr.indexOf = BP.indexOf + arr.copy = BP.copy + arr.slice = BP.slice + arr.readUIntLE = BP.readUIntLE + arr.readUIntBE = BP.readUIntBE + arr.readUInt8 = BP.readUInt8 + arr.readUInt16LE = BP.readUInt16LE + arr.readUInt16BE = BP.readUInt16BE + arr.readUInt32LE = BP.readUInt32LE + arr.readUInt32BE = BP.readUInt32BE + arr.readIntLE = BP.readIntLE + arr.readIntBE = BP.readIntBE + arr.readInt8 = BP.readInt8 + arr.readInt16LE = BP.readInt16LE + arr.readInt16BE = BP.readInt16BE + arr.readInt32LE = BP.readInt32LE + arr.readInt32BE = BP.readInt32BE + arr.readFloatLE = BP.readFloatLE + arr.readFloatBE = BP.readFloatBE + arr.readDoubleLE = BP.readDoubleLE + arr.readDoubleBE = BP.readDoubleBE + arr.writeUInt8 = BP.writeUInt8 + arr.writeUIntLE = BP.writeUIntLE + arr.writeUIntBE = BP.writeUIntBE + arr.writeUInt16LE = BP.writeUInt16LE + arr.writeUInt16BE = BP.writeUInt16BE + arr.writeUInt32LE = BP.writeUInt32LE + arr.writeUInt32BE = BP.writeUInt32BE + arr.writeIntLE = BP.writeIntLE + arr.writeIntBE = BP.writeIntBE + arr.writeInt8 = BP.writeInt8 + arr.writeInt16LE = BP.writeInt16LE + arr.writeInt16BE = BP.writeInt16BE + arr.writeInt32LE = BP.writeInt32LE + arr.writeInt32BE = BP.writeInt32BE + arr.writeFloatLE = BP.writeFloatLE + arr.writeFloatBE = BP.writeFloatBE + arr.writeDoubleLE = BP.writeDoubleLE + arr.writeDoubleBE = BP.writeDoubleBE + arr.fill = BP.fill + arr.inspect = BP.inspect + arr.toArrayBuffer = BP.toArrayBuffer + + return arr +} + +var INVALID_BASE64_RE = /[^+\/0-9A-z\-]/g + +function base64clean (str) { + // Node strips out invalid characters like \n and \t from the string, base64-js does not + str = stringtrim(str).replace(INVALID_BASE64_RE, '') + // Node converts strings with length < 2 to '' + if (str.length < 2) return '' + // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not + while (str.length % 4 !== 0) { + str = str + '=' + } + return str +} + +function stringtrim (str) { + if (str.trim) return str.trim() + return str.replace(/^\s+|\s+$/g, '') +} + +function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) +} + +function utf8ToBytes (string, units) { + units = units || Infinity + var codePoint + var length = string.length + var leadSurrogate = null + var bytes = [] + var i = 0 + + for (; i < length; i++) { + codePoint = string.charCodeAt(i) + + // is surrogate component + if (codePoint > 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (leadSurrogate) { + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue + } else { + // valid surrogate pair + codePoint = leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00 | 0x10000 + leadSurrogate = null + } + } else { + // no lead yet + + if (codePoint > 0xDBFF) { + // unexpected trail + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else if (i + 1 === length) { + // unpaired lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else { + // valid lead + leadSurrogate = codePoint + continue + } + } + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = null + } + + // encode utf8 + if (codePoint < 0x80) { + if ((units -= 1) < 0) break + bytes.push(codePoint) + } else if (codePoint < 0x800) { + if ((units -= 2) < 0) break + bytes.push( + codePoint >> 0x6 | 0xC0, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x10000) { + if ((units -= 3) < 0) break + bytes.push( + codePoint >> 0xC | 0xE0, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x200000) { + if ((units -= 4) < 0) break + bytes.push( + codePoint >> 0x12 | 0xF0, + codePoint >> 0xC & 0x3F | 0x80, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else { + throw new Error('Invalid code point') + } + } + + return bytes +} + +function asciiToBytes (str) { + var byteArray = [] + for (var i = 0; i < str.length; i++) { + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push(str.charCodeAt(i) & 0xFF) + } + return byteArray +} + +function utf16leToBytes (str, units) { + var c, hi, lo + var byteArray = [] + for (var i = 0; i < str.length; i++) { + if ((units -= 2) < 0) break + + c = str.charCodeAt(i) + hi = c >> 8 + lo = c % 256 + byteArray.push(lo) + byteArray.push(hi) + } + + return byteArray +} + +function base64ToBytes (str) { + return base64.toByteArray(base64clean(str)) +} + +function blitBuffer (src, dst, offset, length) { + for (var i = 0; i < length; i++) { + if ((i + offset >= dst.length) || (i >= src.length)) break + dst[i + offset] = src[i] + } + return i +} + +function decodeUtf8Char (str) { + try { + return decodeURIComponent(str) + } catch (err) { + return String.fromCharCode(0xFFFD) // UTF 8 invalid char + } +} + +},{"base64-js":2,"ieee754":3,"is-array":4}],2:[function(require,module,exports){ +var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + +;(function (exports) { + 'use strict'; + + var Arr = (typeof Uint8Array !== 'undefined') + ? Uint8Array + : Array + + var PLUS = '+'.charCodeAt(0) + var SLASH = '/'.charCodeAt(0) + var NUMBER = '0'.charCodeAt(0) + var LOWER = 'a'.charCodeAt(0) + var UPPER = 'A'.charCodeAt(0) + var PLUS_URL_SAFE = '-'.charCodeAt(0) + var SLASH_URL_SAFE = '_'.charCodeAt(0) + + function decode (elt) { + var code = elt.charCodeAt(0) + if (code === PLUS || + code === PLUS_URL_SAFE) + return 62 // '+' + if (code === SLASH || + code === SLASH_URL_SAFE) + return 63 // '/' + if (code < NUMBER) + return -1 //no match + if (code < NUMBER + 10) + return code - NUMBER + 26 + 26 + if (code < UPPER + 26) + return code - UPPER + if (code < LOWER + 26) + return code - LOWER + 26 + } + + function b64ToByteArray (b64) { + var i, j, l, tmp, placeHolders, arr + + if (b64.length % 4 > 0) { + throw new Error('Invalid string. Length must be a multiple of 4') + } + + // the number of equal signs (place holders) + // if there are two placeholders, than the two characters before it + // represent one byte + // if there is only one, then the three characters before it represent 2 bytes + // this is just a cheap hack to not do indexOf twice + var len = b64.length + placeHolders = '=' === b64.charAt(len - 2) ? 2 : '=' === b64.charAt(len - 1) ? 1 : 0 + + // base64 is 4/3 + up to two characters of the original data + arr = new Arr(b64.length * 3 / 4 - placeHolders) + + // if there are placeholders, only get up to the last complete 4 chars + l = placeHolders > 0 ? b64.length - 4 : b64.length + + var L = 0 + + function push (v) { + arr[L++] = v + } + + for (i = 0, j = 0; i < l; i += 4, j += 3) { + tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3)) + push((tmp & 0xFF0000) >> 16) + push((tmp & 0xFF00) >> 8) + push(tmp & 0xFF) + } + + if (placeHolders === 2) { + tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4) + push(tmp & 0xFF) + } else if (placeHolders === 1) { + tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2) + push((tmp >> 8) & 0xFF) + push(tmp & 0xFF) + } + + return arr + } + + function uint8ToBase64 (uint8) { + var i, + extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes + output = "", + temp, length + + function encode (num) { + return lookup.charAt(num) + } + + function tripletToBase64 (num) { + return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F) + } + + // go through the array every three bytes, we'll deal with trailing stuff later + for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { + temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]) + output += tripletToBase64(temp) + } + + // pad the end with zeros, but make sure to not forget the extra bytes + switch (extraBytes) { + case 1: + temp = uint8[uint8.length - 1] + output += encode(temp >> 2) + output += encode((temp << 4) & 0x3F) + output += '==' + break + case 2: + temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]) + output += encode(temp >> 10) + output += encode((temp >> 4) & 0x3F) + output += encode((temp << 2) & 0x3F) + output += '=' + break + } + + return output + } + + exports.toByteArray = b64ToByteArray + exports.fromByteArray = uint8ToBase64 +}(typeof exports === 'undefined' ? (this.base64js = {}) : exports)) + +},{}],3:[function(require,module,exports){ +exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m, + eLen = nBytes * 8 - mLen - 1, + eMax = (1 << eLen) - 1, + eBias = eMax >> 1, + nBits = -7, + i = isLE ? (nBytes - 1) : 0, + d = isLE ? -1 : 1, + s = buffer[offset + i] + + i += d + + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + if (e === 0) { + e = 1 - eBias + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity) + } else { + m = m + Math.pow(2, mLen) + e = e - eBias + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) +} + +exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c, + eLen = nBytes * 8 - mLen - 1, + eMax = (1 << eLen) - 1, + eBias = eMax >> 1, + rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0), + i = isLE ? 0 : (nBytes - 1), + d = isLE ? 1 : -1, + s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 + + value = Math.abs(value) + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0 + e = eMax + } else { + e = Math.floor(Math.log(value) / Math.LN2) + if (value * (c = Math.pow(2, -e)) < 1) { + e-- + c *= 2 + } + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 + } + + if (e + eBias >= eMax) { + m = 0 + e = eMax + } else if (e + eBias >= 1) { + m = (value * c - 1) * Math.pow(2, mLen) + e = e + eBias + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) + e = 0 + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + + buffer[offset + i - d] |= s * 128 +} + +},{}],4:[function(require,module,exports){ + +/** + * isArray + */ + +var isArray = Array.isArray; + +/** + * toString + */ + +var str = Object.prototype.toString; + +/** + * Whether or not the given `val` + * is an array. + * + * example: + * + * isArray([]); + * // > true + * isArray(arguments); + * // > false + * isArray(''); + * // > false + * + * @param {mixed} val + * @return {bool} + */ + +module.exports = isArray || function (val) { + return !! val && '[object Array]' == str.call(val); +}; + +},{}],5:[function(require,module,exports){ +(function (Buffer){ +//prototype class for hash functions +function Hash (blockSize, finalSize) { + this._block = new Buffer(blockSize) //new Uint32Array(blockSize/4) + this._finalSize = finalSize + this._blockSize = blockSize + this._len = 0 + this._s = 0 +} + +Hash.prototype.update = function (data, enc) { + if ("string" === typeof data) { + enc = enc || "utf8" + data = new Buffer(data, enc) + } + + var l = this._len += data.length + var s = this._s || 0 + var f = 0 + var buffer = this._block + + while (s < l) { + var t = Math.min(data.length, f + this._blockSize - (s % this._blockSize)) + var ch = (t - f) + + for (var i = 0; i < ch; i++) { + buffer[(s % this._blockSize) + i] = data[i + f] + } + + s += ch + f += ch + + if ((s % this._blockSize) === 0) { + this._update(buffer) + } + } + this._s = s + + return this +} + +Hash.prototype.digest = function (enc) { + // Suppose the length of the message M, in bits, is l + var l = this._len * 8 + + // Append the bit 1 to the end of the message + this._block[this._len % this._blockSize] = 0x80 + + // and then k zero bits, where k is the smallest non-negative solution to the equation (l + 1 + k) === finalSize mod blockSize + this._block.fill(0, this._len % this._blockSize + 1) + + if (l % (this._blockSize * 8) >= this._finalSize * 8) { + this._update(this._block) + this._block.fill(0) + } + + // to this append the block which is equal to the number l written in binary + // TODO: handle case where l is > Math.pow(2, 29) + this._block.writeInt32BE(l, this._blockSize - 4) + + var hash = this._update(this._block) || this._hash() + + return enc ? hash.toString(enc) : hash +} + +Hash.prototype._update = function () { + throw new Error('_update must be implemented by subclass') +} + +module.exports = Hash + +}).call(this,require("buffer").Buffer) +},{"buffer":1}],6:[function(require,module,exports){ +var exports = module.exports = function (alg) { + var Alg = exports[alg.toLowerCase()] + if(!Alg) throw new Error(alg + ' is not supported (we accept pull requests)') + return new Alg() +} + + +exports.sha = require('./sha') +exports.sha1 = require('./sha1') +exports.sha224 = require('./sha224') +exports.sha256 = require('./sha256') +exports.sha384 = require('./sha384') +exports.sha512 = require('./sha512') + +},{"./sha":8,"./sha1":9,"./sha224":10,"./sha256":11,"./sha384":12,"./sha512":13}],7:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + +},{}],8:[function(require,module,exports){ +(function (Buffer){ +/* + * A JavaScript implementation of the Secure Hash Algorithm, SHA-0, as defined + * in FIPS PUB 180-1 + * This source code is derived from sha1.js of the same repository. + * The difference between SHA-0 and SHA-1 is just a bitwise rotate left + * operation was added. + */ + +var inherits = require('inherits') +var Hash = require('./hash') + +var W = new Array(80) + +function Sha() { + this.init() + this._w = W + + Hash.call(this, 64, 56) +} + +inherits(Sha, Hash) + +Sha.prototype.init = function () { + this._a = 0x67452301 + this._b = 0xefcdab89 + this._c = 0x98badcfe + this._d = 0x10325476 + this._e = 0xc3d2e1f0 + + return this +} + +/* + * Bitwise rotate a 32-bit number to the left. + */ +function rol(num, cnt) { + return (num << cnt) | (num >>> (32 - cnt)); +} + +Sha.prototype._update = function (M) { + var W = this._w + + var a = this._a + var b = this._b + var c = this._c + var d = this._d + var e = this._e + + var j = 0, k + + /* + * SHA-1 has a bitwise rotate left operation. But, SHA is not + * function calcW() { return rol(W[j - 3] ^ W[j - 8] ^ W[j - 14] ^ W[j - 16], 1) } + */ + function calcW() { return W[j - 3] ^ W[j - 8] ^ W[j - 14] ^ W[j - 16] } + function loop(w, f) { + W[j] = w + + var t = rol(a, 5) + f + e + w + k + + e = d + d = c + c = rol(b, 30) + b = a + a = t + j++ + } + + k = 1518500249 + while (j < 16) loop(M.readInt32BE(j * 4), (b & c) | ((~b) & d)) + while (j < 20) loop(calcW(), (b & c) | ((~b) & d)) + k = 1859775393 + while (j < 40) loop(calcW(), b ^ c ^ d) + k = -1894007588 + while (j < 60) loop(calcW(), (b & c) | (b & d) | (c & d)) + k = -899497514 + while (j < 80) loop(calcW(), b ^ c ^ d) + + this._a = (a + this._a) | 0 + this._b = (b + this._b) | 0 + this._c = (c + this._c) | 0 + this._d = (d + this._d) | 0 + this._e = (e + this._e) | 0 +} + +Sha.prototype._hash = function () { + var H = new Buffer(20) + + H.writeInt32BE(this._a|0, 0) + H.writeInt32BE(this._b|0, 4) + H.writeInt32BE(this._c|0, 8) + H.writeInt32BE(this._d|0, 12) + H.writeInt32BE(this._e|0, 16) + + return H +} + +module.exports = Sha + + +}).call(this,require("buffer").Buffer) +},{"./hash":5,"buffer":1,"inherits":7}],9:[function(require,module,exports){ +(function (Buffer){ +/* + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS PUB 180-1 + * Version 2.1a Copyright Paul Johnston 2000 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + */ + +var inherits = require('inherits') +var Hash = require('./hash') + +var W = new Array(80) + +function Sha1() { + this.init() + this._w = W + + Hash.call(this, 64, 56) +} + +inherits(Sha1, Hash) + +Sha1.prototype.init = function () { + this._a = 0x67452301 + this._b = 0xefcdab89 + this._c = 0x98badcfe + this._d = 0x10325476 + this._e = 0xc3d2e1f0 + + return this +} + +/* + * Bitwise rotate a 32-bit number to the left. + */ +function rol(num, cnt) { + return (num << cnt) | (num >>> (32 - cnt)); +} + +Sha1.prototype._update = function (M) { + var W = this._w + + var a = this._a + var b = this._b + var c = this._c + var d = this._d + var e = this._e + + var j = 0, k + + function calcW() { return rol(W[j - 3] ^ W[j - 8] ^ W[j - 14] ^ W[j - 16], 1) } + function loop(w, f) { + W[j] = w + + var t = rol(a, 5) + f + e + w + k + + e = d + d = c + c = rol(b, 30) + b = a + a = t + j++ + } + + k = 1518500249 + while (j < 16) loop(M.readInt32BE(j * 4), (b & c) | ((~b) & d)) + while (j < 20) loop(calcW(), (b & c) | ((~b) & d)) + k = 1859775393 + while (j < 40) loop(calcW(), b ^ c ^ d) + k = -1894007588 + while (j < 60) loop(calcW(), (b & c) | (b & d) | (c & d)) + k = -899497514 + while (j < 80) loop(calcW(), b ^ c ^ d) + + this._a = (a + this._a) | 0 + this._b = (b + this._b) | 0 + this._c = (c + this._c) | 0 + this._d = (d + this._d) | 0 + this._e = (e + this._e) | 0 +} + +Sha1.prototype._hash = function () { + var H = new Buffer(20) + + H.writeInt32BE(this._a|0, 0) + H.writeInt32BE(this._b|0, 4) + H.writeInt32BE(this._c|0, 8) + H.writeInt32BE(this._d|0, 12) + H.writeInt32BE(this._e|0, 16) + + return H +} + +module.exports = Sha1 + + +}).call(this,require("buffer").Buffer) +},{"./hash":5,"buffer":1,"inherits":7}],10:[function(require,module,exports){ +(function (Buffer){ +/** + * A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined + * in FIPS 180-2 + * Version 2.2-beta Copyright Angel Marin, Paul Johnston 2000 - 2009. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * + */ + +var inherits = require('inherits') +var SHA256 = require('./sha256') +var Hash = require('./hash') + +var W = new Array(64) + +function Sha224() { + this.init() + + this._w = W // new Array(64) + + Hash.call(this, 64, 56) +} + +inherits(Sha224, SHA256) + +Sha224.prototype.init = function () { + this._a = 0xc1059ed8|0 + this._b = 0x367cd507|0 + this._c = 0x3070dd17|0 + this._d = 0xf70e5939|0 + this._e = 0xffc00b31|0 + this._f = 0x68581511|0 + this._g = 0x64f98fa7|0 + this._h = 0xbefa4fa4|0 + + return this +} + +Sha224.prototype._hash = function () { + var H = new Buffer(28) + + H.writeInt32BE(this._a, 0) + H.writeInt32BE(this._b, 4) + H.writeInt32BE(this._c, 8) + H.writeInt32BE(this._d, 12) + H.writeInt32BE(this._e, 16) + H.writeInt32BE(this._f, 20) + H.writeInt32BE(this._g, 24) + + return H +} + +module.exports = Sha224 + +}).call(this,require("buffer").Buffer) +},{"./hash":5,"./sha256":11,"buffer":1,"inherits":7}],11:[function(require,module,exports){ +(function (Buffer){ +/** + * A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined + * in FIPS 180-2 + * Version 2.2-beta Copyright Angel Marin, Paul Johnston 2000 - 2009. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * + */ + +var inherits = require('inherits') +var Hash = require('./hash') + +var K = [ + 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, + 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, + 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, + 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, + 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, + 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, + 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, + 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, + 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2 +] + +var W = new Array(64) + +function Sha256() { + this.init() + + this._w = W // new Array(64) + + Hash.call(this, 64, 56) +} + +inherits(Sha256, Hash) + +Sha256.prototype.init = function () { + this._a = 0x6a09e667|0 + this._b = 0xbb67ae85|0 + this._c = 0x3c6ef372|0 + this._d = 0xa54ff53a|0 + this._e = 0x510e527f|0 + this._f = 0x9b05688c|0 + this._g = 0x1f83d9ab|0 + this._h = 0x5be0cd19|0 + + return this +} + +function S (X, n) { + return (X >>> n) | (X << (32 - n)); +} + +function R (X, n) { + return (X >>> n); +} + +function Ch (x, y, z) { + return ((x & y) ^ ((~x) & z)); +} + +function Maj (x, y, z) { + return ((x & y) ^ (x & z) ^ (y & z)); +} + +function Sigma0256 (x) { + return (S(x, 2) ^ S(x, 13) ^ S(x, 22)); +} + +function Sigma1256 (x) { + return (S(x, 6) ^ S(x, 11) ^ S(x, 25)); +} + +function Gamma0256 (x) { + return (S(x, 7) ^ S(x, 18) ^ R(x, 3)); +} + +function Gamma1256 (x) { + return (S(x, 17) ^ S(x, 19) ^ R(x, 10)); +} + +Sha256.prototype._update = function(M) { + var W = this._w + + var a = this._a | 0 + var b = this._b | 0 + var c = this._c | 0 + var d = this._d | 0 + var e = this._e | 0 + var f = this._f | 0 + var g = this._g | 0 + var h = this._h | 0 + + var j = 0 + + function calcW() { return Gamma1256(W[j - 2]) + W[j - 7] + Gamma0256(W[j - 15]) + W[j - 16] } + function loop(w) { + W[j] = w + + var T1 = h + Sigma1256(e) + Ch(e, f, g) + K[j] + w + var T2 = Sigma0256(a) + Maj(a, b, c); + + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++ + } + + while (j < 16) loop(M.readInt32BE(j * 4)) + while (j < 64) loop(calcW()) + + this._a = (a + this._a) | 0 + this._b = (b + this._b) | 0 + this._c = (c + this._c) | 0 + this._d = (d + this._d) | 0 + this._e = (e + this._e) | 0 + this._f = (f + this._f) | 0 + this._g = (g + this._g) | 0 + this._h = (h + this._h) | 0 +}; + +Sha256.prototype._hash = function () { + var H = new Buffer(32) + + H.writeInt32BE(this._a, 0) + H.writeInt32BE(this._b, 4) + H.writeInt32BE(this._c, 8) + H.writeInt32BE(this._d, 12) + H.writeInt32BE(this._e, 16) + H.writeInt32BE(this._f, 20) + H.writeInt32BE(this._g, 24) + H.writeInt32BE(this._h, 28) + + return H +} + +module.exports = Sha256 + +}).call(this,require("buffer").Buffer) +},{"./hash":5,"buffer":1,"inherits":7}],12:[function(require,module,exports){ +(function (Buffer){ +var inherits = require('inherits') +var SHA512 = require('./sha512'); +var Hash = require('./hash') + +var W = new Array(160) + +function Sha384() { + this.init() + this._w = W + + Hash.call(this, 128, 112) +} + +inherits(Sha384, SHA512) + +Sha384.prototype.init = function () { + this._a = 0xcbbb9d5d|0 + this._b = 0x629a292a|0 + this._c = 0x9159015a|0 + this._d = 0x152fecd8|0 + this._e = 0x67332667|0 + this._f = 0x8eb44a87|0 + this._g = 0xdb0c2e0d|0 + this._h = 0x47b5481d|0 + + this._al = 0xc1059ed8|0 + this._bl = 0x367cd507|0 + this._cl = 0x3070dd17|0 + this._dl = 0xf70e5939|0 + this._el = 0xffc00b31|0 + this._fl = 0x68581511|0 + this._gl = 0x64f98fa7|0 + this._hl = 0xbefa4fa4|0 + + return this +} + +Sha384.prototype._hash = function () { + var H = new Buffer(48) + + function writeInt64BE(h, l, offset) { + H.writeInt32BE(h, offset) + H.writeInt32BE(l, offset + 4) + } + + writeInt64BE(this._a, this._al, 0) + writeInt64BE(this._b, this._bl, 8) + writeInt64BE(this._c, this._cl, 16) + writeInt64BE(this._d, this._dl, 24) + writeInt64BE(this._e, this._el, 32) + writeInt64BE(this._f, this._fl, 40) + + return H +} + +module.exports = Sha384 + +}).call(this,require("buffer").Buffer) +},{"./hash":5,"./sha512":13,"buffer":1,"inherits":7}],13:[function(require,module,exports){ +(function (Buffer){ +var inherits = require('inherits') +var Hash = require('./hash') + +var K = [ + 0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, + 0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc, + 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019, + 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118, + 0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe, + 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2, + 0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, + 0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694, + 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3, + 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65, + 0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483, + 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5, + 0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, + 0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4, + 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725, + 0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70, + 0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926, + 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df, + 0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, + 0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b, + 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001, + 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30, + 0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910, + 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8, + 0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, + 0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8, + 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb, + 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3, + 0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60, + 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec, + 0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, + 0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b, + 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207, + 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178, + 0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6, + 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b, + 0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, + 0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c, + 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a, + 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817 +] + +var W = new Array(160) + +function Sha512() { + this.init() + this._w = W + + Hash.call(this, 128, 112) +} + +inherits(Sha512, Hash) + +Sha512.prototype.init = function () { + this._a = 0x6a09e667|0 + this._b = 0xbb67ae85|0 + this._c = 0x3c6ef372|0 + this._d = 0xa54ff53a|0 + this._e = 0x510e527f|0 + this._f = 0x9b05688c|0 + this._g = 0x1f83d9ab|0 + this._h = 0x5be0cd19|0 + + this._al = 0xf3bcc908|0 + this._bl = 0x84caa73b|0 + this._cl = 0xfe94f82b|0 + this._dl = 0x5f1d36f1|0 + this._el = 0xade682d1|0 + this._fl = 0x2b3e6c1f|0 + this._gl = 0xfb41bd6b|0 + this._hl = 0x137e2179|0 + + return this +} + +function S (X, Xl, n) { + return (X >>> n) | (Xl << (32 - n)) +} + +function Ch (x, y, z) { + return ((x & y) ^ ((~x) & z)); +} + +function Maj (x, y, z) { + return ((x & y) ^ (x & z) ^ (y & z)); +} + +Sha512.prototype._update = function(M) { + var W = this._w + + var a = this._a | 0 + var b = this._b | 0 + var c = this._c | 0 + var d = this._d | 0 + var e = this._e | 0 + var f = this._f | 0 + var g = this._g | 0 + var h = this._h | 0 + + var al = this._al | 0 + var bl = this._bl | 0 + var cl = this._cl | 0 + var dl = this._dl | 0 + var el = this._el | 0 + var fl = this._fl | 0 + var gl = this._gl | 0 + var hl = this._hl | 0 + + var i = 0, j = 0 + var Wi, Wil + function calcW() { + var x = W[j - 15*2] + var xl = W[j - 15*2 + 1] + var gamma0 = S(x, xl, 1) ^ S(x, xl, 8) ^ (x >>> 7) + var gamma0l = S(xl, x, 1) ^ S(xl, x, 8) ^ S(xl, x, 7) + + x = W[j - 2*2] + xl = W[j - 2*2 + 1] + var gamma1 = S(x, xl, 19) ^ S(xl, x, 29) ^ (x >>> 6) + var gamma1l = S(xl, x, 19) ^ S(x, xl, 29) ^ S(xl, x, 6) + + // W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16] + var Wi7 = W[j - 7*2] + var Wi7l = W[j - 7*2 + 1] + + var Wi16 = W[j - 16*2] + var Wi16l = W[j - 16*2 + 1] + + Wil = gamma0l + Wi7l + Wi = gamma0 + Wi7 + ((Wil >>> 0) < (gamma0l >>> 0) ? 1 : 0) + Wil = Wil + gamma1l + Wi = Wi + gamma1 + ((Wil >>> 0) < (gamma1l >>> 0) ? 1 : 0) + Wil = Wil + Wi16l + Wi = Wi + Wi16 + ((Wil >>> 0) < (Wi16l >>> 0) ? 1 : 0) + } + + function loop() { + W[j] = Wi + W[j + 1] = Wil + + var maj = Maj(a, b, c) + var majl = Maj(al, bl, cl) + + var sigma0h = S(a, al, 28) ^ S(al, a, 2) ^ S(al, a, 7) + var sigma0l = S(al, a, 28) ^ S(a, al, 2) ^ S(a, al, 7) + var sigma1h = S(e, el, 14) ^ S(e, el, 18) ^ S(el, e, 9) + var sigma1l = S(el, e, 14) ^ S(el, e, 18) ^ S(e, el, 9) + + // t1 = h + sigma1 + ch + K[i] + W[i] + var Ki = K[j] + var Kil = K[j + 1] + + var ch = Ch(e, f, g) + var chl = Ch(el, fl, gl) + + var t1l = hl + sigma1l + var t1 = h + sigma1h + ((t1l >>> 0) < (hl >>> 0) ? 1 : 0) + t1l = t1l + chl + t1 = t1 + ch + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0) + t1l = t1l + Kil + t1 = t1 + Ki + ((t1l >>> 0) < (Kil >>> 0) ? 1 : 0) + t1l = t1l + Wil + t1 = t1 + Wi + ((t1l >>> 0) < (Wil >>> 0) ? 1 : 0) + + // t2 = sigma0 + maj + var t2l = sigma0l + majl + var t2 = sigma0h + maj + ((t2l >>> 0) < (sigma0l >>> 0) ? 1 : 0) + + h = g + hl = gl + g = f + gl = fl + f = e + fl = el + el = (dl + t1l) | 0 + e = (d + t1 + ((el >>> 0) < (dl >>> 0) ? 1 : 0)) | 0 + d = c + dl = cl + c = b + cl = bl + b = a + bl = al + al = (t1l + t2l) | 0 + a = (t1 + t2 + ((al >>> 0) < (t1l >>> 0) ? 1 : 0)) | 0 + + i++ + j += 2 + } + + while (i < 16) { + Wi = M.readInt32BE(j * 4) + Wil = M.readInt32BE(j * 4 + 4) + + loop() + } + + while (i < 80) { + calcW() + loop() + } + + this._al = (this._al + al) | 0 + this._bl = (this._bl + bl) | 0 + this._cl = (this._cl + cl) | 0 + this._dl = (this._dl + dl) | 0 + this._el = (this._el + el) | 0 + this._fl = (this._fl + fl) | 0 + this._gl = (this._gl + gl) | 0 + this._hl = (this._hl + hl) | 0 + + this._a = (this._a + a + ((this._al >>> 0) < (al >>> 0) ? 1 : 0)) | 0 + this._b = (this._b + b + ((this._bl >>> 0) < (bl >>> 0) ? 1 : 0)) | 0 + this._c = (this._c + c + ((this._cl >>> 0) < (cl >>> 0) ? 1 : 0)) | 0 + this._d = (this._d + d + ((this._dl >>> 0) < (dl >>> 0) ? 1 : 0)) | 0 + this._e = (this._e + e + ((this._el >>> 0) < (el >>> 0) ? 1 : 0)) | 0 + this._f = (this._f + f + ((this._fl >>> 0) < (fl >>> 0) ? 1 : 0)) | 0 + this._g = (this._g + g + ((this._gl >>> 0) < (gl >>> 0) ? 1 : 0)) | 0 + this._h = (this._h + h + ((this._hl >>> 0) < (hl >>> 0) ? 1 : 0)) | 0 +} + +Sha512.prototype._hash = function () { + var H = new Buffer(64) + + function writeInt64BE(h, l, offset) { + H.writeInt32BE(h, offset) + H.writeInt32BE(l, offset + 4) + } + + writeInt64BE(this._a, this._al, 0) + writeInt64BE(this._b, this._bl, 8) + writeInt64BE(this._c, this._cl, 16) + writeInt64BE(this._d, this._dl, 24) + writeInt64BE(this._e, this._el, 32) + writeInt64BE(this._f, this._fl, 40) + writeInt64BE(this._g, this._gl, 48) + writeInt64BE(this._h, this._hl, 56) + + return H +} + +module.exports = Sha512 + +}).call(this,require("buffer").Buffer) +},{"./hash":5,"buffer":1,"inherits":7}],14:[function(require,module,exports){ +Elm.Native.Sha = {}; +Elm.Native.Sha.make = function(elm) { + elm.Native = elm.Native || {}; + elm.Native.Sha = elm.Native.Sha || {}; + if (elm.Native.Sha.values) { + return elm.Native.Sha.values; + } + + function update(data, inputEncoding, algorithm) { + return algorithm.update(data, inputEncoding); + } + + function digest(encoding, hash) { + return hash.digest(encoding); + } + + return Elm.Native.Sha.values = { + createHash: require('sha.js'), + update: F3(update), + digest: F2(digest) + }; +}; +},{"sha.js":6}]},{},[14]); +; + +Elm.Native.Show = {}; +Elm.Native.Show.make = function(localRuntime) { + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Show = localRuntime.Native.Show || {}; + if (localRuntime.Native.Show.values) + { + return localRuntime.Native.Show.values; + } + + var _Array; + var Dict; + var List; + var Utils = Elm.Native.Utils.make(localRuntime); + + var toString = function(v) + { + var type = typeof v; + if (type === "function") + { + var name = v.func ? v.func.name : v.name; + return ''; + } + else if (type === "boolean") + { + return v ? "True" : "False"; + } + else if (type === "number") + { + return v + ""; + } + else if ((v instanceof String) && v.isChar) + { + return "'" + addSlashes(v, true) + "'"; + } + else if (type === "string") + { + return '"' + addSlashes(v, false) + '"'; + } + else if (type === "object" && '_' in v && probablyPublic(v)) + { + var output = []; + for (var k in v._) + { + for (var i = v._[k].length; i--; ) + { + output.push(k + " = " + toString(v._[k][i])); + } + } + for (var k in v) + { + if (k === '_') continue; + output.push(k + " = " + toString(v[k])); + } + if (output.length === 0) + { + return "{}"; + } + return "{ " + output.join(", ") + " }"; + } + else if (type === "object" && 'ctor' in v) + { + if (v.ctor.substring(0,6) === "_Tuple") + { + var output = []; + for (var k in v) + { + if (k === 'ctor') continue; + output.push(toString(v[k])); + } + return "(" + output.join(",") + ")"; + } + else if (v.ctor === "_Array") + { + if (!_Array) + { + _Array = Elm.Array.make(localRuntime); + } + var list = _Array.toList(v); + return "Array.fromList " + toString(list); + } + else if (v.ctor === "::") + { + var output = '[' + toString(v._0); + v = v._1; + while (v.ctor === "::") + { + output += "," + toString(v._0); + v = v._1; + } + return output + ']'; + } + else if (v.ctor === "[]") + { + return "[]"; + } + else if (v.ctor === "RBNode" || v.ctor === "RBEmpty") + { + if (!Dict) + { + Dict = Elm.Dict.make(localRuntime); + } + if (!List) + { + List = Elm.List.make(localRuntime); + } + var list = Dict.toList(v); + var name = "Dict"; + if (list.ctor === "::" && list._0._1.ctor === "_Tuple0") + { + name = "Set"; + list = A2(List.map, function(x){return x._0}, list); + } + return name + ".fromList " + toString(list); + } + else if (v.ctor.slice(0,5) === "Text:") + { + return '' + } + else + { + var output = ""; + for (var i in v) + { + if (i === 'ctor') continue; + var str = toString(v[i]); + var parenless = str[0] === '{' || str[0] === '<' || str.indexOf(' ') < 0; + output += ' ' + (parenless ? str : '(' + str + ')'); + } + return v.ctor + output; + } + } + if (type === 'object' && 'notify' in v && 'id' in v) + { + return 'initialValue' in v + ? '' + : ''; + } + return ""; + }; + + function addSlashes(str, isChar) + { + var s = str.replace(/\\/g, '\\\\') + .replace(/\n/g, '\\n') + .replace(/\t/g, '\\t') + .replace(/\r/g, '\\r') + .replace(/\v/g, '\\v') + .replace(/\0/g, '\\0'); + if (isChar) + { + return s.replace(/\'/g, "\\'") + } + else + { + return s.replace(/\"/g, '\\"'); + } + } + + function probablyPublic(v) + { + var keys = Object.keys(v); + var len = keys.length; + if (len === 3 + && 'props' in v + && 'element' in v) + { + return false; + } + else if (len === 5 + && 'horizontal' in v + && 'vertical' in v + && 'x' in v + && 'y' in v) + { + return false; + } + else if (len === 7 + && 'theta' in v + && 'scale' in v + && 'x' in v + && 'y' in v + && 'alpha' in v + && 'form' in v) + { + return false; + } + return true; + } + + return localRuntime.Native.Show.values = { + toString: toString + }; +}; + +Elm.Native.Signal = {}; +Elm.Native.Signal.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Signal = localRuntime.Native.Signal || {}; + if (localRuntime.Native.Signal.values) + { + return localRuntime.Native.Signal.values; + } + + + var Task = Elm.Native.Task.make(localRuntime); + var Utils = Elm.Native.Utils.make(localRuntime); + + + function broadcastToKids(node, timestamp, update) + { + var kids = node.kids; + for (var i = kids.length; i--; ) + { + kids[i].notify(timestamp, update, node.id); + } + } + + + // INPUT + + function input(name, base) + { + var node = { + id: Utils.guid(), + name: 'input-' + name, + value: base, + parents: [], + kids: [] + }; + + node.notify = function(timestamp, targetId, value) { + var update = targetId === node.id; + if (update) + { + node.value = value; + } + broadcastToKids(node, timestamp, update); + return update; + }; + + localRuntime.inputs.push(node); + + return node; + } + + function constant(value) + { + return input('constant', value); + } + + + // MAILBOX + + function mailbox(base) + { + var signal = input('mailbox', base); + + function send(value) { + return Task.asyncFunction(function(callback) { + localRuntime.setTimeout(function() { + localRuntime.notify(signal.id, value); + }, 0); + callback(Task.succeed(Utils.Tuple0)); + }); + } + + return { + _: {}, + signal: signal, + address: { + ctor: 'Address', + _0: send + } + }; + } + + function sendMessage(message) + { + Task.perform(message._0); + } + + + // OUTPUT + + function output(name, handler, parent) + { + var node = { + id: Utils.guid(), + name: 'output-' + name, + parents: [parent], + isOutput: true + }; + + node.notify = function(timestamp, parentUpdate, parentID) + { + if (parentUpdate) + { + handler(parent.value); + } + }; + + parent.kids.push(node); + + return node; + } + + + // MAP + + function mapMany(refreshValue, args) + { + var node = { + id: Utils.guid(), + name: 'map' + args.length, + value: refreshValue(), + parents: args, + kids: [] + }; + + var numberOfParents = args.length; + var count = 0; + var update = false; + + node.notify = function(timestamp, parentUpdate, parentID) + { + ++count; + + update = update || parentUpdate; + + if (count === numberOfParents) + { + if (update) + { + node.value = refreshValue(); + } + broadcastToKids(node, timestamp, update); + update = false; + count = 0; + } + }; + + for (var i = numberOfParents; i--; ) + { + args[i].kids.push(node); + } + + return node; + } + + + function map(func, a) + { + function refreshValue() + { + return func(a.value); + } + return mapMany(refreshValue, [a]); + } + + + function map2(func, a, b) + { + function refreshValue() + { + return A2( func, a.value, b.value ); + } + return mapMany(refreshValue, [a,b]); + } + + + function map3(func, a, b, c) + { + function refreshValue() + { + return A3( func, a.value, b.value, c.value ); + } + return mapMany(refreshValue, [a,b,c]); + } + + + function map4(func, a, b, c, d) + { + function refreshValue() + { + return A4( func, a.value, b.value, c.value, d.value ); + } + return mapMany(refreshValue, [a,b,c,d]); + } + + + function map5(func, a, b, c, d, e) + { + function refreshValue() + { + return A5( func, a.value, b.value, c.value, d.value, e.value ); + } + return mapMany(refreshValue, [a,b,c,d,e]); + } + + + + // FOLD + + function foldp(update, state, signal) + { + var node = { + id: Utils.guid(), + name: 'foldp', + parents: [signal], + kids: [], + value: state + }; + + node.notify = function(timestamp, parentUpdate, parentID) + { + if (parentUpdate) + { + node.value = A2( update, signal.value, node.value ); + } + broadcastToKids(node, timestamp, parentUpdate); + }; + + signal.kids.push(node); + + return node; + } + + + // TIME + + function timestamp(signal) + { + var node = { + id: Utils.guid(), + name: 'timestamp', + value: Utils.Tuple2(localRuntime.timer.programStart, signal.value), + parents: [signal], + kids: [] + }; + + node.notify = function(timestamp, parentUpdate, parentID) + { + if (parentUpdate) + { + node.value = Utils.Tuple2(timestamp, signal.value); + } + broadcastToKids(node, timestamp, parentUpdate); + }; + + signal.kids.push(node); + + return node; + } + + + function delay(time, signal) + { + var delayed = input('delay-input-' + time, signal.value); + + function handler(value) + { + setTimeout(function() { + localRuntime.notify(delayed.id, value); + }, time); + } + + output('delay-output-' + time, handler, signal); + + return delayed; + } + + + // MERGING + + function genericMerge(tieBreaker, leftStream, rightStream) + { + var node = { + id: Utils.guid(), + name: 'merge', + value: A2(tieBreaker, leftStream.value, rightStream.value), + parents: [leftStream, rightStream], + kids: [] + }; + + var left = { touched: false, update: false, value: null }; + var right = { touched: false, update: false, value: null }; + + node.notify = function(timestamp, parentUpdate, parentID) + { + if (parentID === leftStream.id) + { + left.touched = true; + left.update = parentUpdate; + left.value = leftStream.value; + } + if (parentID === rightStream.id) + { + right.touched = true; + right.update = parentUpdate; + right.value = rightStream.value; + } + + if (left.touched && right.touched) + { + var update = false; + if (left.update && right.update) + { + node.value = A2(tieBreaker, left.value, right.value); + update = true; + } + else if (left.update) + { + node.value = left.value; + update = true; + } + else if (right.update) + { + node.value = right.value; + update = true; + } + left.touched = false; + right.touched = false; + + broadcastToKids(node, timestamp, update); + } + }; + + leftStream.kids.push(node); + rightStream.kids.push(node); + + return node; + } + + + // FILTERING + + function filterMap(toMaybe, base, signal) + { + var maybe = toMaybe(signal.value); + var node = { + id: Utils.guid(), + name: 'filterMap', + value: maybe.ctor === 'Nothing' ? base : maybe._0, + parents: [signal], + kids: [] + }; + + node.notify = function(timestamp, parentUpdate, parentID) + { + var update = false; + if (parentUpdate) + { + var maybe = toMaybe(signal.value); + if (maybe.ctor === 'Just') + { + update = true; + node.value = maybe._0; + } + } + broadcastToKids(node, timestamp, update); + }; + + signal.kids.push(node); + + return node; + } + + + // SAMPLING + + function sampleOn(ticker, signal) + { + var node = { + id: Utils.guid(), + name: 'sampleOn', + value: signal.value, + parents: [ticker, signal], + kids: [] + }; + + var signalTouch = false; + var tickerTouch = false; + var tickerUpdate = false; + + node.notify = function(timestamp, parentUpdate, parentID) + { + if (parentID === ticker.id) + { + tickerTouch = true; + tickerUpdate = parentUpdate; + } + if (parentID === signal.id) + { + signalTouch = true; + } + + if (tickerTouch && signalTouch) + { + if (tickerUpdate) + { + node.value = signal.value; + } + tickerTouch = false; + signalTouch = false; + + broadcastToKids(node, timestamp, tickerUpdate); + } + }; + + ticker.kids.push(node); + signal.kids.push(node); + + return node; + } + + + // DROP REPEATS + + function dropRepeats(signal) + { + var node = { + id: Utils.guid(), + name: 'dropRepeats', + value: signal.value, + parents: [signal], + kids: [] + }; + + node.notify = function(timestamp, parentUpdate, parentID) + { + var update = false; + if (parentUpdate && !Utils.eq(node.value, signal.value)) + { + node.value = signal.value; + update = true; + } + broadcastToKids(node, timestamp, update); + }; + + signal.kids.push(node); + + return node; + } + + + return localRuntime.Native.Signal.values = { + input: input, + constant: constant, + mailbox: mailbox, + sendMessage: sendMessage, + output: output, + map: F2(map), + map2: F3(map2), + map3: F4(map3), + map4: F5(map4), + map5: F6(map5), + foldp: F3(foldp), + genericMerge: F3(genericMerge), + filterMap: F3(filterMap), + sampleOn: F2(sampleOn), + dropRepeats: dropRepeats, + timestamp: timestamp, + delay: F2(delay) + }; +}; + +Elm.Native.String = {}; +Elm.Native.String.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.String = localRuntime.Native.String || {}; + if (localRuntime.Native.String.values) + { + return localRuntime.Native.String.values; + } + if ('values' in Elm.Native.String) + { + return localRuntime.Native.String.values = Elm.Native.String.values; + } + + + var Char = Elm.Char.make(localRuntime); + var List = Elm.Native.List.make(localRuntime); + var Maybe = Elm.Maybe.make(localRuntime); + var Result = Elm.Result.make(localRuntime); + var Utils = Elm.Native.Utils.make(localRuntime); + + function isEmpty(str) + { + return str.length === 0; + } + function cons(chr,str) + { + return chr + str; + } + function uncons(str) + { + var hd; + return (hd = str[0]) + ? Maybe.Just(Utils.Tuple2(Utils.chr(hd), str.slice(1))) + : Maybe.Nothing; + } + function append(a,b) + { + return a + b; + } + function concat(strs) + { + return List.toArray(strs).join(''); + } + function length(str) + { + return str.length; + } + function map(f,str) + { + var out = str.split(''); + for (var i = out.length; i--; ) + { + out[i] = f(Utils.chr(out[i])); + } + return out.join(''); + } + function filter(pred,str) + { + return str.split('').map(Utils.chr).filter(pred).join(''); + } + function reverse(str) + { + return str.split('').reverse().join(''); + } + function foldl(f,b,str) + { + var len = str.length; + for (var i = 0; i < len; ++i) + { + b = A2(f, Utils.chr(str[i]), b); + } + return b; + } + function foldr(f,b,str) + { + for (var i = str.length; i--; ) + { + b = A2(f, Utils.chr(str[i]), b); + } + return b; + } + + function split(sep, str) + { + return List.fromArray(str.split(sep)); + } + function join(sep, strs) + { + return List.toArray(strs).join(sep); + } + function repeat(n, str) + { + var result = ''; + while (n > 0) + { + if (n & 1) + { + result += str; + } + n >>= 1, str += str; + } + return result; + } + + function slice(start, end, str) + { + return str.slice(start,end); + } + function left(n, str) + { + return n < 1 ? "" : str.slice(0,n); + } + function right(n, str) + { + return n < 1 ? "" : str.slice(-n); + } + function dropLeft(n, str) + { + return n < 1 ? str : str.slice(n); + } + function dropRight(n, str) + { + return n < 1 ? str : str.slice(0,-n); + } + + function pad(n,chr,str) + { + var half = (n - str.length) / 2; + return repeat(Math.ceil(half),chr) + str + repeat(half|0,chr); + } + function padRight(n,chr,str) + { + return str + repeat(n - str.length, chr); + } + function padLeft(n,chr,str) + { + return repeat(n - str.length, chr) + str; + } + + function trim(str) + { + return str.trim(); + } + function trimLeft(str) + { + return str.trimLeft(); + } + function trimRight(str) + { + return str.trimRight(); + } + + function words(str) + { + return List.fromArray(str.trim().split(/\s+/g)); + } + function lines(str) + { + return List.fromArray(str.split(/\r\n|\r|\n/g)); + } + + function toUpper(str) + { + return str.toUpperCase(); + } + function toLower(str) + { + return str.toLowerCase(); + } + + function any(pred, str) + { + for (var i = str.length; i--; ) + { + if (pred(Utils.chr(str[i]))) + { + return true; + } + } + return false; + } + function all(pred, str) + { + for (var i = str.length; i--; ) + { + if (!pred(Utils.chr(str[i]))) + { + return false; + } + } + return true; + } + + function contains(sub, str) + { + return str.indexOf(sub) > -1; + } + function startsWith(sub, str) + { + return str.indexOf(sub) === 0; + } + function endsWith(sub, str) + { + return str.length >= sub.length && + str.lastIndexOf(sub) === str.length - sub.length; + } + function indexes(sub, str) + { + var subLen = sub.length; + var i = 0; + var is = []; + while ((i = str.indexOf(sub, i)) > -1) + { + is.push(i); + i = i + subLen; + } + return List.fromArray(is); + } + + function toInt(s) + { + var len = s.length; + if (len === 0) + { + return Result.Err("could not convert string '" + s + "' to an Int" ); + } + var start = 0; + if (s[0] == '-') + { + if (len === 1) + { + return Result.Err("could not convert string '" + s + "' to an Int" ); + } + start = 1; + } + for (var i = start; i < len; ++i) + { + if (!Char.isDigit(s[i])) + { + return Result.Err("could not convert string '" + s + "' to an Int" ); + } + } + return Result.Ok(parseInt(s, 10)); + } + + function toFloat(s) + { + var len = s.length; + if (len === 0) + { + return Result.Err("could not convert string '" + s + "' to a Float" ); + } + var start = 0; + if (s[0] == '-') + { + if (len === 1) + { + return Result.Err("could not convert string '" + s + "' to a Float" ); + } + start = 1; + } + var dotCount = 0; + for (var i = start; i < len; ++i) + { + if (Char.isDigit(s[i])) + { + continue; + } + if (s[i] === '.') + { + dotCount += 1; + if (dotCount <= 1) + { + continue; + } + } + return Result.Err("could not convert string '" + s + "' to a Float" ); + } + return Result.Ok(parseFloat(s)); + } + + function toList(str) + { + return List.fromArray(str.split('').map(Utils.chr)); + } + function fromList(chars) + { + return List.toArray(chars).join(''); + } + + return Elm.Native.String.values = { + isEmpty: isEmpty, + cons: F2(cons), + uncons: uncons, + append: F2(append), + concat: concat, + length: length, + map: F2(map), + filter: F2(filter), + reverse: reverse, + foldl: F3(foldl), + foldr: F3(foldr), + + split: F2(split), + join: F2(join), + repeat: F2(repeat), + + slice: F3(slice), + left: F2(left), + right: F2(right), + dropLeft: F2(dropLeft), + dropRight: F2(dropRight), + + pad: F3(pad), + padLeft: F3(padLeft), + padRight: F3(padRight), + + trim: trim, + trimLeft: trimLeft, + trimRight: trimRight, + + words: words, + lines: lines, + + toUpper: toUpper, + toLower: toLower, + + any: F2(any), + all: F2(all), + + contains: F2(contains), + startsWith: F2(startsWith), + endsWith: F2(endsWith), + indexes: F2(indexes), + + toInt: toInt, + toFloat: toFloat, + toList: toList, + fromList: fromList + }; +}; + +Elm.Native.Task = {}; +Elm.Native.Task.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Task = localRuntime.Native.Task || {}; + if (localRuntime.Native.Task.values) + { + return localRuntime.Native.Task.values; + } + + var Result = Elm.Result.make(localRuntime); + var Signal; + var Utils = Elm.Native.Utils.make(localRuntime); + + + // CONSTRUCTORS + + function succeed(value) + { + return { + tag: 'Succeed', + value: value + }; + } + + function fail(error) + { + return { + tag: 'Fail', + value: error + }; + } + + function asyncFunction(func) + { + return { + tag: 'Async', + asyncFunction: func + }; + } + + function andThen(task, callback) + { + return { + tag: 'AndThen', + task: task, + callback: callback + }; + } + + function catch_(task, callback) + { + return { + tag: 'Catch', + task: task, + callback: callback + }; + } + + + // RUNNER + + function perform(task) { + runTask({ task: task }, function() {}); + } + + function performSignal(name, signal) + { + var workQueue = []; + + function onComplete() + { + workQueue.shift(); + + setTimeout(function() { + if (workQueue.length > 0) + { + runTask(workQueue[0], onComplete); + } + }, 0); + } + + function register(task) + { + var root = { task: task }; + workQueue.push(root); + if (workQueue.length === 1) + { + runTask(root, onComplete); + } + } + + if (!Signal) + { + Signal = Elm.Native.Signal.make(localRuntime); + } + Signal.output('perform-tasks-' + name, register, signal); + + register(signal.value); + + return signal; + } + + function mark(status, task) + { + return { status: status, task: task }; + } + + function runTask(root, onComplete) + { + var result = mark('runnable', root.task); + while (result.status === 'runnable') + { + result = stepTask(onComplete, root, result.task); + } + + if (result.status === 'done') + { + root.task = result.task; + onComplete(); + } + + if (result.status === 'blocked') + { + root.task = result.task; + } + } + + function stepTask(onComplete, root, task) + { + var tag = task.tag; + + if (tag === 'Succeed' || tag === 'Fail') + { + return mark('done', task); + } + + if (tag === 'Async') + { + var placeHolder = {}; + var couldBeSync = true; + var wasSync = false; + + task.asyncFunction(function(result) { + placeHolder.tag = result.tag; + placeHolder.value = result.value; + if (couldBeSync) + { + wasSync = true; + } + else + { + runTask(root, onComplete); + } + }); + couldBeSync = false; + return mark(wasSync ? 'done' : 'blocked', placeHolder); + } + + if (tag === 'AndThen' || tag === 'Catch') + { + var result = mark('runnable', task.task); + while (result.status === 'runnable') + { + result = stepTask(onComplete, root, result.task); + } + + if (result.status === 'done') + { + var activeTask = result.task; + var activeTag = activeTask.tag; + + var succeedChain = activeTag === 'Succeed' && tag === 'AndThen'; + var failChain = activeTag === 'Fail' && tag === 'Catch'; + + return (succeedChain || failChain) + ? mark('runnable', task.callback(activeTask.value)) + : mark('runnable', activeTask); + } + if (result.status === 'blocked') + { + return mark('blocked', { + tag: tag, + task: result.task, + callback: task.callback + }); + } + } + } + + + // THREADS + + function sleep(time) { + return asyncFunction(function(callback) { + setTimeout(function() { + callback(succeed(Utils.Tuple0)); + }, time); + }); + } + + function spawn(task) { + return asyncFunction(function(callback) { + var id = setTimeout(function() { + perform(task); + }, 0); + callback(succeed(id)); + }); + } + + + return localRuntime.Native.Task.values = { + succeed: succeed, + fail: fail, + asyncFunction: asyncFunction, + andThen: F2(andThen), + catch_: F2(catch_), + perform: perform, + performSignal: performSignal, + spawn: spawn, + sleep: sleep + }; +}; + +Elm.Native.Text = {}; +Elm.Native.Text.make = function(localRuntime) { + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Text = localRuntime.Native.Text || {}; + if (localRuntime.Native.Text.values) + { + return localRuntime.Native.Text.values; + } + + var toCss = Elm.Native.Color.make(localRuntime).toCss; + var List = Elm.Native.List.make(localRuntime); + + + // CONSTRUCTORS + + function fromString(str) + { + return { + ctor: 'Text:Text', + _0: str + }; + } + + function append(a, b) + { + return { + ctor: 'Text:Append', + _0: a, + _1: b + }; + } + + function addMeta(field, value, text) + { + var newProps = {}; + var newText = { + ctor: 'Text:Meta', + _0: newProps, + _1: text + }; + + if (text.ctor === 'Text:Meta') + { + newText._1 = text._1; + var props = text._0; + for (var i = metaKeys.length; i--; ) + { + var key = metaKeys[i]; + var val = props[key]; + if (val) + { + newProps[key] = val; + } + } + } + newProps[field] = value; + return newText; + } + + var metaKeys = [ + 'font-size', + 'font-family', + 'font-style', + 'font-weight', + 'href', + 'text-decoration', + 'color' + ]; + + + // conversions from Elm values to CSS + + function toTypefaces(list) + { + var typefaces = List.toArray(list); + for (var i = typefaces.length; i--; ) + { + var typeface = typefaces[i]; + if (typeface.indexOf(' ') > -1) + { + typefaces[i] = "'" + typeface + "'"; + } + } + return typefaces.join(','); + } + + function toLine(line) + { + var ctor = line.ctor; + return ctor === 'Under' + ? 'underline' + : ctor === 'Over' + ? 'overline' + : 'line-through'; + } + + // setting styles of Text + + function style(style, text) + { + var newText = addMeta('color', toCss(style.color), text); + var props = newText._0; + + if (style.typeface.ctor !== '[]') + { + props['font-family'] = toTypefaces(style.typeface); + } + if (style.height.ctor !== "Nothing") + { + props['font-size'] = style.height._0 + 'px'; + } + if (style.bold) + { + props['font-weight'] = 'bold'; + } + if (style.italic) + { + props['font-style'] = 'italic'; + } + if (style.line.ctor !== 'Nothing') + { + props['text-decoration'] = toLine(style.line._0); + } + return newText; + } + + function height(px, text) + { + return addMeta('font-size', px + 'px', text); + } + + function typeface(names, text) + { + return addMeta('font-family', toTypefaces(names), text); + } + + function monospace(text) + { + return addMeta('font-family', 'monospace', text); + } + + function italic(text) + { + return addMeta('font-style', 'italic', text); + } + + function bold(text) + { + return addMeta('font-weight', 'bold', text); + } + + function link(href, text) + { + return addMeta('href', href, text); + } + + function line(line, text) + { + return addMeta('text-decoration', toLine(line), text); + } + + function color(color, text) + { + return addMeta('color', toCss(color), text);; + } + + + // RENDER + + function renderHtml(text) + { + var tag = text.ctor; + if (tag === 'Text:Append') + { + return renderHtml(text._0) + renderHtml(text._1); + } + if (tag === 'Text:Text') + { + return properEscape(text._0); + } + if (tag === 'Text:Meta') + { + return renderMeta(text._0, renderHtml(text._1)); + } + } + + function renderMeta(metas, string) + { + var href = metas['href']; + if (href) + { + string = '
    ' + string + ''; + } + var styles = ''; + for (var key in metas) + { + if (key === 'href') + { + continue; + } + styles += key + ':' + metas[key] + ';'; + } + if (styles) + { + string = '' + string + ''; + } + return string; + } + + function properEscape(str) + { + if (str.length == 0) + { + return str; + } + str = str //.replace(/&/g, "&") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(//g, ">"); + var arr = str.split('\n'); + for (var i = arr.length; i--; ) + { + arr[i] = makeSpaces(arr[i]); + } + return arr.join('
    '); + } + + function makeSpaces(s) + { + if (s.length == 0) + { + return s; + } + var arr = s.split(''); + if (arr[0] == ' ') + { + arr[0] = " " + } + for (var i = arr.length; --i; ) + { + if (arr[i][0] == ' ' && arr[i-1] == ' ') + { + arr[i-1] = arr[i-1] + arr[i]; + arr[i] = ''; + } + } + for (var i = arr.length; i--; ) + { + if (arr[i].length > 1 && arr[i][0] == ' ') + { + var spaces = arr[i].split(''); + for (var j = spaces.length - 2; j >= 0; j -= 2) + { + spaces[j] = ' '; + } + arr[i] = spaces.join(''); + } + } + arr = arr.join(''); + if (arr[arr.length-1] === " ") + { + return arr.slice(0,-1) + ' '; + } + return arr; + } + + + return localRuntime.Native.Text.values = { + fromString: fromString, + append: F2(append), + + height: F2(height), + italic: italic, + bold: bold, + line: F2(line), + monospace: monospace, + typeface: F2(typeface), + color: F2(color), + link: F2(link), + style: F2(style), + + toTypefaces: toTypefaces, + toLine: toLine, + renderHtml: renderHtml + }; +}; + +Elm.Native.Transform2D = {}; +Elm.Native.Transform2D.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Transform2D = localRuntime.Native.Transform2D || {}; + if (localRuntime.Native.Transform2D.values) + { + return localRuntime.Native.Transform2D.values; + } + + var A; + if (typeof Float32Array === 'undefined') + { + A = function(arr) + { + this.length = arr.length; + this[0] = arr[0]; + this[1] = arr[1]; + this[2] = arr[2]; + this[3] = arr[3]; + this[4] = arr[4]; + this[5] = arr[5]; + }; + } + else + { + A = Float32Array; + } + + // layout of matrix in an array is + // + // | m11 m12 dx | + // | m21 m22 dy | + // | 0 0 1 | + // + // new A([ m11, m12, dx, m21, m22, dy ]) + + var identity = new A([1,0,0,0,1,0]); + function matrix(m11, m12, m21, m22, dx, dy) + { + return new A([m11, m12, dx, m21, m22, dy]); + } + + function rotation(t) + { + var c = Math.cos(t); + var s = Math.sin(t); + return new A([c, -s, 0, s, c, 0]); + } + + function rotate(t,m) + { + var c = Math.cos(t); + var s = Math.sin(t); + var m11 = m[0], m12 = m[1], m21 = m[3], m22 = m[4]; + return new A([m11*c + m12*s, -m11*s + m12*c, m[2], + m21*c + m22*s, -m21*s + m22*c, m[5]]); + } + /* + function move(xy,m) { + var x = xy._0; + var y = xy._1; + var m11 = m[0], m12 = m[1], m21 = m[3], m22 = m[4]; + return new A([m11, m12, m11*x + m12*y + m[2], + m21, m22, m21*x + m22*y + m[5]]); + } + function scale(s,m) { return new A([m[0]*s, m[1]*s, m[2], m[3]*s, m[4]*s, m[5]]); } + function scaleX(x,m) { return new A([m[0]*x, m[1], m[2], m[3]*x, m[4], m[5]]); } + function scaleY(y,m) { return new A([m[0], m[1]*y, m[2], m[3], m[4]*y, m[5]]); } + function reflectX(m) { return new A([-m[0], m[1], m[2], -m[3], m[4], m[5]]); } + function reflectY(m) { return new A([m[0], -m[1], m[2], m[3], -m[4], m[5]]); } + + function transform(m11, m21, m12, m22, mdx, mdy, n) { + var n11 = n[0], n12 = n[1], n21 = n[3], n22 = n[4], ndx = n[2], ndy = n[5]; + return new A([m11*n11 + m12*n21, + m11*n12 + m12*n22, + m11*ndx + m12*ndy + mdx, + m21*n11 + m22*n21, + m21*n12 + m22*n22, + m21*ndx + m22*ndy + mdy]); + } + */ + function multiply(m, n) + { + var m11 = m[0], m12 = m[1], m21 = m[3], m22 = m[4], mdx = m[2], mdy = m[5]; + var n11 = n[0], n12 = n[1], n21 = n[3], n22 = n[4], ndx = n[2], ndy = n[5]; + return new A([m11*n11 + m12*n21, + m11*n12 + m12*n22, + m11*ndx + m12*ndy + mdx, + m21*n11 + m22*n21, + m21*n12 + m22*n22, + m21*ndx + m22*ndy + mdy]); + } + + return localRuntime.Native.Transform2D.values = { + identity:identity, + matrix:F6(matrix), + rotation:rotation, + multiply:F2(multiply) + /* + transform:F7(transform), + rotate:F2(rotate), + move:F2(move), + scale:F2(scale), + scaleX:F2(scaleX), + scaleY:F2(scaleY), + reflectX:reflectX, + reflectY:reflectY + */ + }; + +}; + +Elm.Native = Elm.Native || {}; +Elm.Native.Utils = {}; +Elm.Native.Utils.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Utils = localRuntime.Native.Utils || {}; + if (localRuntime.Native.Utils.values) + { + return localRuntime.Native.Utils.values; + } + + function eq(l,r) + { + var stack = [{'x': l, 'y': r}] + while (stack.length > 0) + { + var front = stack.pop(); + var x = front.x; + var y = front.y; + if (x === y) + { + continue; + } + if (typeof x === "object") + { + var c = 0; + for (var i in x) + { + ++c; + if (i in y) + { + if (i !== 'ctor') + { + stack.push({ 'x': x[i], 'y': y[i] }); + } + } + else + { + return false; + } + } + if ('ctor' in x) + { + stack.push({'x': x.ctor, 'y': y.ctor}); + } + if (c !== Object.keys(y).length) + { + return false; + } + } + else if (typeof x === 'function') + { + throw new Error('Equality error: general function equality is ' + + 'undecidable, and therefore, unsupported'); + } + else + { + return false; + } + } + return true; + } + + // code in Generate/JavaScript.hs depends on the particular + // integer values assigned to LT, EQ, and GT + var LT = -1, EQ = 0, GT = 1, ord = ['LT','EQ','GT']; + + function compare(x,y) + { + return { + ctor: ord[cmp(x,y)+1] + }; + } + + function cmp(x,y) { + var ord; + if (typeof x !== 'object') + { + return x === y ? EQ : x < y ? LT : GT; + } + else if (x.isChar) + { + var a = x.toString(); + var b = y.toString(); + return a === b + ? EQ + : a < b + ? LT + : GT; + } + else if (x.ctor === "::" || x.ctor === "[]") + { + while (true) + { + if (x.ctor === "[]" && y.ctor === "[]") + { + return EQ; + } + if (x.ctor !== y.ctor) + { + return x.ctor === '[]' ? LT : GT; + } + ord = cmp(x._0, y._0); + if (ord !== EQ) + { + return ord; + } + x = x._1; + y = y._1; + } + } + else if (x.ctor.slice(0,6) === '_Tuple') + { + var n = x.ctor.slice(6) - 0; + var err = 'cannot compare tuples with more than 6 elements.'; + if (n === 0) return EQ; + if (n >= 1) { ord = cmp(x._0, y._0); if (ord !== EQ) return ord; + if (n >= 2) { ord = cmp(x._1, y._1); if (ord !== EQ) return ord; + if (n >= 3) { ord = cmp(x._2, y._2); if (ord !== EQ) return ord; + if (n >= 4) { ord = cmp(x._3, y._3); if (ord !== EQ) return ord; + if (n >= 5) { ord = cmp(x._4, y._4); if (ord !== EQ) return ord; + if (n >= 6) { ord = cmp(x._5, y._5); if (ord !== EQ) return ord; + if (n >= 7) throw new Error('Comparison error: ' + err); } } } } } } + return EQ; + } + else + { + throw new Error('Comparison error: comparison is only defined on ints, ' + + 'floats, times, chars, strings, lists of comparable values, ' + + 'and tuples of comparable values.'); + } + } + + + var Tuple0 = { + ctor: "_Tuple0" + }; + + function Tuple2(x,y) + { + return { + ctor: "_Tuple2", + _0: x, + _1: y + }; + } + + function chr(c) + { + var x = new String(c); + x.isChar = true; + return x; + } + + function txt(str) + { + var t = new String(str); + t.text = true; + return t; + } + + var count = 0; + function guid(_) + { + return count++ + } + + function copy(oldRecord) + { + var newRecord = {}; + for (var key in oldRecord) + { + var value = key === '_' + ? copy(oldRecord._) + : oldRecord[key]; + newRecord[key] = value; + } + return newRecord; + } + + function remove(key, oldRecord) + { + var record = copy(oldRecord); + if (key in record._) + { + record[key] = record._[key][0]; + record._[key] = record._[key].slice(1); + if (record._[key].length === 0) + { + delete record._[key]; + } + } + else + { + delete record[key]; + } + return record; + } + + function replace(keyValuePairs, oldRecord) + { + var record = copy(oldRecord); + for (var i = keyValuePairs.length; i--; ) + { + var pair = keyValuePairs[i]; + record[pair[0]] = pair[1]; + } + return record; + } + + function insert(key, value, oldRecord) + { + var newRecord = copy(oldRecord); + if (key in newRecord) + { + var values = newRecord._[key]; + var copiedValues = values ? values.slice(0) : []; + newRecord._[key] = [newRecord[key]].concat(copiedValues); + } + newRecord[key] = value; + return newRecord; + } + + function getXY(e) + { + var posx = 0; + var posy = 0; + if (e.pageX || e.pageY) + { + posx = e.pageX; + posy = e.pageY; + } + else if (e.clientX || e.clientY) + { + posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + + if (localRuntime.isEmbed()) + { + var rect = localRuntime.node.getBoundingClientRect(); + var relx = rect.left + document.body.scrollLeft + document.documentElement.scrollLeft; + var rely = rect.top + document.body.scrollTop + document.documentElement.scrollTop; + // TODO: figure out if there is a way to avoid rounding here + posx = posx - Math.round(relx) - localRuntime.node.clientLeft; + posy = posy - Math.round(rely) - localRuntime.node.clientTop; + } + return Tuple2(posx, posy); + } + + + //// LIST STUFF //// + + var Nil = { ctor:'[]' }; + + function Cons(hd,tl) + { + return { + ctor: "::", + _0: hd, + _1: tl + }; + } + + function append(xs,ys) + { + // append Strings + if (typeof xs === "string") + { + return xs + ys; + } + + // append Text + if (xs.ctor.slice(0,5) === 'Text:') + { + return { + ctor: 'Text:Append', + _0: xs, + _1: ys + }; + } + + + + // append Lists + if (xs.ctor === '[]') + { + return ys; + } + var root = Cons(xs._0, Nil); + var curr = root; + xs = xs._1; + while (xs.ctor !== '[]') + { + curr._1 = Cons(xs._0, Nil); + xs = xs._1; + curr = curr._1; + } + curr._1 = ys; + return root; + } + + //// RUNTIME ERRORS //// + + function indent(lines) + { + return '\n' + lines.join('\n'); + } + + function badCase(moduleName, span) + { + var msg = indent([ + 'Non-exhaustive pattern match in case-expression.', + 'Make sure your patterns cover every case!' + ]); + throw new Error('Runtime error in module ' + moduleName + ' (' + span + ')' + msg); + } + + function badIf(moduleName, span) + { + var msg = indent([ + 'Non-exhaustive pattern match in multi-way-if expression.', + 'It is best to use \'otherwise\' as the last branch of multi-way-if.' + ]); + throw new Error('Runtime error in module ' + moduleName + ' (' + span + ')' + msg); + } + + + function badPort(expected, received) + { + var msg = indent([ + 'Expecting ' + expected + ' but was given ', + JSON.stringify(received) + ]); + throw new Error('Runtime error when sending values through a port.' + msg); + } + + + return localRuntime.Native.Utils.values = { + eq: eq, + cmp: cmp, + compare: F2(compare), + Tuple0: Tuple0, + Tuple2: Tuple2, + chr: chr, + txt: txt, + copy: copy, + remove: remove, + replace: replace, + insert: insert, + guid: guid, + getXY: getXY, + + Nil: Nil, + Cons: Cons, + append: F2(append), + + badCase: badCase, + badIf: badIf, + badPort: badPort + }; +}; + +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { + var globalEvent = new ProxyEvent(ev); + globalEvent.currentTarget = delegatorTarget; + callListeners(globalHandlers, globalEvent) + } + + findAndInvokeListeners(ev.target, ev, eventName) + } +} + +function findAndInvokeListeners(elem, ev, eventName) { + var listener = getListener(elem, eventName) + + if (listener && listener.handlers.length > 0) { + var listenerEvent = new ProxyEvent(ev); + listenerEvent.currentTarget = listener.currentTarget + callListeners(listener.handlers, listenerEvent) + + if (listenerEvent._bubbles) { + var nextTarget = listener.currentTarget.parentNode + findAndInvokeListeners(nextTarget, ev, eventName) + } + } +} + +function getListener(target, type) { + // terminate recursion if parent is `null` + if (target === null) { + return null + } + + var ds = DataSet(target) + // fetch list of handler fns for this event + var handler = ds[type] + var allHandler = ds.event + + if (!handler && !allHandler) { + return getListener(target.parentNode, type) + } + + var handlers = [].concat(handler || [], allHandler || []) + return new Listener(target, handlers) +} + +function callListeners(handlers, ev) { + handlers.forEach(function (handler) { + if (typeof handler === "function") { + handler(ev) + } else if (typeof handler.handleEvent === "function") { + handler.handleEvent(ev) + } else if (handler.type === "dom-delegator-handle") { + HANDLER_STORE(handler).func(ev) + } else { + throw new Error("dom-delegator: unknown handler " + + "found: " + JSON.stringify(handlers)); + } + }) +} + +function Listener(target, handlers) { + this.currentTarget = target + this.handlers = handlers +} + +function Handle() { + this.type = "dom-delegator-handle" +} + +},{"./add-event.js":6,"./proxy-event.js":15,"./remove-event.js":16,"data-set":2,"global/document":10,"weakmap-shim/create-store":13}],8:[function(require,module,exports){ +var Individual = require("individual") +var cuid = require("cuid") +var globalDocument = require("global/document") + +var DOMDelegator = require("./dom-delegator.js") + +var delegatorCache = Individual("__DOM_DELEGATOR_CACHE@9", { + delegators: {} +}) +var commonEvents = [ + "blur", "change", "click", "contextmenu", "dblclick", + "error","focus", "focusin", "focusout", "input", "keydown", + "keypress", "keyup", "load", "mousedown", "mouseup", + "resize", "scroll", "select", "submit", "touchcancel", + "touchend", "touchstart", "unload" +] + +/* Delegator is a thin wrapper around a singleton `DOMDelegator` + instance. + + Only one DOMDelegator should exist because we do not want + duplicate event listeners bound to the DOM. + + `Delegator` will also `listenTo()` all events unless + every caller opts out of it +*/ +module.exports = Delegator + +function Delegator(opts) { + opts = opts || {} + var document = opts.document || globalDocument + + var cacheKey = document["__DOM_DELEGATOR_CACHE_TOKEN@9"] + + if (!cacheKey) { + cacheKey = + document["__DOM_DELEGATOR_CACHE_TOKEN@9"] = cuid() + } + + var delegator = delegatorCache.delegators[cacheKey] + + if (!delegator) { + delegator = delegatorCache.delegators[cacheKey] = + new DOMDelegator(document) + } + + if (opts.defaultEvents !== false) { + for (var i = 0; i < commonEvents.length; i++) { + delegator.listenTo(commonEvents[i]) + } + } + + return delegator +} + + + +},{"./dom-delegator.js":7,"cuid":9,"global/document":10,"individual":11}],9:[function(require,module,exports){ +/** + * cuid.js + * Collision-resistant UID generator for browsers and node. + * Sequential for fast db lookups and recency sorting. + * Safe for element IDs and server-side lookups. + * + * Extracted from CLCTR + * + * Copyright (c) Eric Elliott 2012 + * MIT License + */ + +/*global window, navigator, document, require, process, module */ +(function (app) { + 'use strict'; + var namespace = 'cuid', + c = 0, + blockSize = 4, + base = 36, + discreteValues = Math.pow(base, blockSize), + + pad = function pad(num, size) { + var s = "000000000" + num; + return s.substr(s.length-size); + }, + + randomBlock = function randomBlock() { + return pad((Math.random() * + discreteValues << 0) + .toString(base), blockSize); + }, + + safeCounter = function () { + c = (c < discreteValues) ? c : 0; + c++; // this is not subliminal + return c - 1; + }, + + api = function cuid() { + // Starting with a lowercase letter makes + // it HTML element ID friendly. + var letter = 'c', // hard-coded allows for sequential access + + // timestamp + // warning: this exposes the exact date and time + // that the uid was created. + timestamp = (new Date().getTime()).toString(base), + + // Prevent same-machine collisions. + counter, + + // A few chars to generate distinct ids for different + // clients (so different computers are far less + // likely to generate the same id) + fingerprint = api.fingerprint(), + + // Grab some more chars from Math.random() + random = randomBlock() + randomBlock(); + + counter = pad(safeCounter().toString(base), blockSize); + + return (letter + timestamp + counter + fingerprint + random); + }; + + api.slug = function slug() { + var date = new Date().getTime().toString(36), + counter, + print = api.fingerprint().slice(0,1) + + api.fingerprint().slice(-1), + random = randomBlock().slice(-2); + + counter = safeCounter().toString(36).slice(-4); + + return date.slice(-2) + + counter + print + random; + }; + + api.globalCount = function globalCount() { + // We want to cache the results of this + var cache = (function calc() { + var i, + count = 0; + + for (i in window) { + count++; + } + + return count; + }()); + + api.globalCount = function () { return cache; }; + return cache; + }; + + api.fingerprint = function browserPrint() { + return pad((navigator.mimeTypes.length + + navigator.userAgent.length).toString(36) + + api.globalCount().toString(36), 4); + }; + + // don't change anything from here down. + if (app.register) { + app.register(namespace, api); + } else if (typeof module !== 'undefined') { + module.exports = api; + } else { + app[namespace] = api; + } + +}(this.applitude || this)); + +},{}],10:[function(require,module,exports){ +(function (global){ +var topLevel = typeof global !== 'undefined' ? global : + typeof window !== 'undefined' ? window : {} +var minDoc = require('min-document'); + +if (typeof document !== 'undefined') { + module.exports = document; +} else { + var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4']; + + if (!doccy) { + doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc; + } + + module.exports = doccy; +} + +}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"min-document":40}],11:[function(require,module,exports){ +module.exports=require(3) +},{}],12:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + +},{}],13:[function(require,module,exports){ +module.exports=require(4) +},{"./hidden-store.js":14}],14:[function(require,module,exports){ +module.exports=require(5) +},{}],15:[function(require,module,exports){ +var inherits = require("inherits") + +var ALL_PROPS = [ + "altKey", "bubbles", "cancelable", "ctrlKey", + "eventPhase", "metaKey", "relatedTarget", "shiftKey", + "target", "timeStamp", "type", "view", "which" +] +var KEY_PROPS = ["char", "charCode", "key", "keyCode"] +var MOUSE_PROPS = [ + "button", "buttons", "clientX", "clientY", "layerX", + "layerY", "offsetX", "offsetY", "pageX", "pageY", + "screenX", "screenY", "toElement" +] + +var rkeyEvent = /^key|input/ +var rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/ + +module.exports = ProxyEvent + +function ProxyEvent(ev) { + if (!(this instanceof ProxyEvent)) { + return new ProxyEvent(ev) + } + + if (rkeyEvent.test(ev.type)) { + return new KeyEvent(ev) + } else if (rmouseEvent.test(ev.type)) { + return new MouseEvent(ev) + } + + for (var i = 0; i < ALL_PROPS.length; i++) { + var propKey = ALL_PROPS[i] + this[propKey] = ev[propKey] + } + + this._rawEvent = ev + this._bubbles = false; +} + +ProxyEvent.prototype.preventDefault = function () { + this._rawEvent.preventDefault() +} + +ProxyEvent.prototype.startPropagation = function () { + this._bubbles = true; +} + +function MouseEvent(ev) { + for (var i = 0; i < ALL_PROPS.length; i++) { + var propKey = ALL_PROPS[i] + this[propKey] = ev[propKey] + } + + for (var j = 0; j < MOUSE_PROPS.length; j++) { + var mousePropKey = MOUSE_PROPS[j] + this[mousePropKey] = ev[mousePropKey] + } + + this._rawEvent = ev +} + +inherits(MouseEvent, ProxyEvent) + +function KeyEvent(ev) { + for (var i = 0; i < ALL_PROPS.length; i++) { + var propKey = ALL_PROPS[i] + this[propKey] = ev[propKey] + } + + for (var j = 0; j < KEY_PROPS.length; j++) { + var keyPropKey = KEY_PROPS[j] + this[keyPropKey] = ev[keyPropKey] + } + + this._rawEvent = ev +} + +inherits(KeyEvent, ProxyEvent) + +},{"inherits":12}],16:[function(require,module,exports){ +var DataSet = require("data-set") + +module.exports = removeEvent + +function removeEvent(target, type, handler) { + var ds = DataSet(target) + var events = ds[type] + + if (!events) { + return + } else if (Array.isArray(events)) { + var index = events.indexOf(handler) + if (index !== -1) { + events.splice(index, 1) + } + } else if (events === handler) { + ds[type] = null + } +} + +},{"data-set":2}],17:[function(require,module,exports){ +var isObject = require("is-object") +var isHook = require("vtree/is-vhook") + +module.exports = applyProperties + +function applyProperties(node, props, previous) { + for (var propName in props) { + var propValue = props[propName] + + if (propValue === undefined) { + removeProperty(node, props, previous, propName); + } else if (isHook(propValue)) { + propValue.hook(node, + propName, + previous ? previous[propName] : undefined) + } else { + if (isObject(propValue)) { + patchObject(node, props, previous, propName, propValue); + } else if (propValue !== undefined) { + node[propName] = propValue + } + } + } +} + +function removeProperty(node, props, previous, propName) { + if (previous) { + var previousValue = previous[propName] + + if (!isHook(previousValue)) { + if (propName === "attributes") { + for (var attrName in previousValue) { + node.removeAttribute(attrName) + } + } else if (propName === "style") { + for (var i in previousValue) { + node.style[i] = "" + } + } else if (typeof previousValue === "string") { + node[propName] = "" + } else { + node[propName] = null + } + } + } +} + +function patchObject(node, props, previous, propName, propValue) { + var previousValue = previous ? previous[propName] : undefined + + // Set attributes + if (propName === "attributes") { + for (var attrName in propValue) { + var attrValue = propValue[attrName] + + if (attrValue === undefined) { + node.removeAttribute(attrName) + } else { + node.setAttribute(attrName, attrValue) + } + } + + return + } + + if(previousValue && isObject(previousValue) && + getPrototype(previousValue) !== getPrototype(propValue)) { + node[propName] = propValue + return + } + + if (!isObject(node[propName])) { + node[propName] = {} + } + + var replacer = propName === "style" ? "" : undefined + + for (var k in propValue) { + var value = propValue[k] + node[propName][k] = (value === undefined) ? replacer : value + } +} + +function getPrototype(value) { + if (Object.getPrototypeOf) { + return Object.getPrototypeOf(value) + } else if (value.__proto__) { + return value.__proto__ + } else if (value.constructor) { + return value.constructor.prototype + } +} + +},{"is-object":21,"vtree/is-vhook":29}],18:[function(require,module,exports){ +var document = require("global/document") + +var applyProperties = require("./apply-properties") + +var isVNode = require("vtree/is-vnode") +var isVText = require("vtree/is-vtext") +var isWidget = require("vtree/is-widget") +var handleThunk = require("vtree/handle-thunk") + +module.exports = createElement + +function createElement(vnode, opts) { + var doc = opts ? opts.document || document : document + var warn = opts ? opts.warn : null + + vnode = handleThunk(vnode).a + + if (isWidget(vnode)) { + return vnode.init() + } else if (isVText(vnode)) { + return doc.createTextNode(vnode.text) + } else if (!isVNode(vnode)) { + if (warn) { + warn("Item is not a valid virtual dom node", vnode) + } + return null + } + + var node = (vnode.namespace === null) ? + doc.createElement(vnode.tagName) : + doc.createElementNS(vnode.namespace, vnode.tagName) + + var props = vnode.properties + applyProperties(node, props) + + var children = vnode.children + + for (var i = 0; i < children.length; i++) { + var childNode = createElement(children[i], opts) + if (childNode) { + node.appendChild(childNode) + } + } + + return node +} + +},{"./apply-properties":17,"global/document":20,"vtree/handle-thunk":27,"vtree/is-vnode":30,"vtree/is-vtext":31,"vtree/is-widget":32}],19:[function(require,module,exports){ +// Maps a virtual DOM tree onto a real DOM tree in an efficient manner. +// We don't want to read all of the DOM nodes in the tree so we use +// the in-order tree indexing to eliminate recursion down certain branches. +// We only recurse into a DOM node if we know that it contains a child of +// interest. + +var noChild = {} + +module.exports = domIndex + +function domIndex(rootNode, tree, indices, nodes) { + if (!indices || indices.length === 0) { + return {} + } else { + indices.sort(ascending) + return recurse(rootNode, tree, indices, nodes, 0) + } +} + +function recurse(rootNode, tree, indices, nodes, rootIndex) { + nodes = nodes || {} + + + if (rootNode) { + if (indexInRange(indices, rootIndex, rootIndex)) { + nodes[rootIndex] = rootNode + } + + var vChildren = tree.children + + if (vChildren) { + + var childNodes = rootNode.childNodes + + for (var i = 0; i < tree.children.length; i++) { + rootIndex += 1 + + var vChild = vChildren[i] || noChild + var nextIndex = rootIndex + (vChild.count || 0) + + // skip recursion down the tree if there are no nodes down here + if (indexInRange(indices, rootIndex, nextIndex)) { + recurse(childNodes[i], vChild, indices, nodes, rootIndex) + } + + rootIndex = nextIndex + } + } + } + + return nodes +} + +// Binary search for an index in the interval [left, right] +function indexInRange(indices, left, right) { + if (indices.length === 0) { + return false + } + + var minIndex = 0 + var maxIndex = indices.length - 1 + var currentIndex + var currentItem + + while (minIndex <= maxIndex) { + currentIndex = ((maxIndex + minIndex) / 2) >> 0 + currentItem = indices[currentIndex] + + if (minIndex === maxIndex) { + return currentItem >= left && currentItem <= right + } else if (currentItem < left) { + minIndex = currentIndex + 1 + } else if (currentItem > right) { + maxIndex = currentIndex - 1 + } else { + return true + } + } + + return false; +} + +function ascending(a, b) { + return a > b ? 1 : -1 +} + +},{}],20:[function(require,module,exports){ +module.exports=require(10) +},{"min-document":40}],21:[function(require,module,exports){ +module.exports = isObject + +function isObject(x) { + return typeof x === "object" && x !== null +} + +},{}],22:[function(require,module,exports){ +var nativeIsArray = Array.isArray +var toString = Object.prototype.toString + +module.exports = nativeIsArray || isArray + +function isArray(obj) { + return toString.call(obj) === "[object Array]" +} + +},{}],23:[function(require,module,exports){ +var applyProperties = require("./apply-properties") + +var isWidget = require("vtree/is-widget") +var VPatch = require("vtree/vpatch") + +var render = require("./create-element") +var updateWidget = require("./update-widget") + +module.exports = applyPatch + +function applyPatch(vpatch, domNode, renderOptions) { + var type = vpatch.type + var vNode = vpatch.vNode + var patch = vpatch.patch + + switch (type) { + case VPatch.REMOVE: + return removeNode(domNode, vNode) + case VPatch.INSERT: + return insertNode(domNode, patch, renderOptions) + case VPatch.VTEXT: + return stringPatch(domNode, vNode, patch, renderOptions) + case VPatch.WIDGET: + return widgetPatch(domNode, vNode, patch, renderOptions) + case VPatch.VNODE: + return vNodePatch(domNode, vNode, patch, renderOptions) + case VPatch.ORDER: + reorderChildren(domNode, patch) + return domNode + case VPatch.PROPS: + applyProperties(domNode, patch, vNode.properties) + return domNode + case VPatch.THUNK: + return replaceRoot(domNode, + renderOptions.patch(domNode, patch, renderOptions)) + default: + return domNode + } +} + +function removeNode(domNode, vNode) { + var parentNode = domNode.parentNode + + if (parentNode) { + parentNode.removeChild(domNode) + } + + destroyWidget(domNode, vNode); + + return null +} + +function insertNode(parentNode, vNode, renderOptions) { + var newNode = render(vNode, renderOptions) + + if (parentNode) { + parentNode.appendChild(newNode) + } + + return parentNode +} + +function stringPatch(domNode, leftVNode, vText, renderOptions) { + var newNode + + if (domNode.nodeType === 3) { + domNode.replaceData(0, domNode.length, vText.text) + newNode = domNode + } else { + var parentNode = domNode.parentNode + newNode = render(vText, renderOptions) + + if (parentNode) { + parentNode.replaceChild(newNode, domNode) + } + } + + destroyWidget(domNode, leftVNode) + + return newNode +} + +function widgetPatch(domNode, leftVNode, widget, renderOptions) { + if (updateWidget(leftVNode, widget)) { + return widget.update(leftVNode, domNode) || domNode + } + + var parentNode = domNode.parentNode + var newWidget = render(widget, renderOptions) + + if (parentNode) { + parentNode.replaceChild(newWidget, domNode) + } + + destroyWidget(domNode, leftVNode) + + return newWidget +} + +function vNodePatch(domNode, leftVNode, vNode, renderOptions) { + var parentNode = domNode.parentNode + var newNode = render(vNode, renderOptions) + + if (parentNode) { + parentNode.replaceChild(newNode, domNode) + } + + destroyWidget(domNode, leftVNode) + + return newNode +} + +function destroyWidget(domNode, w) { + if (typeof w.destroy === "function" && isWidget(w)) { + w.destroy(domNode) + } +} + +function reorderChildren(domNode, bIndex) { + var children = [] + var childNodes = domNode.childNodes + var len = childNodes.length + var i + var reverseIndex = bIndex.reverse + + for (i = 0; i < len; i++) { + children.push(domNode.childNodes[i]) + } + + var insertOffset = 0 + var move + var node + var insertNode + for (i = 0; i < len; i++) { + move = bIndex[i] + if (move !== undefined && move !== i) { + // the element currently at this index will be moved later so increase the insert offset + if (reverseIndex[i] > i) { + insertOffset++ + } + + node = children[move] + insertNode = childNodes[i + insertOffset] || null + if (node !== insertNode) { + domNode.insertBefore(node, insertNode) + } + + // the moved element came from the front of the array so reduce the insert offset + if (move < i) { + insertOffset-- + } + } + + // element at this index is scheduled to be removed so increase insert offset + if (i in bIndex.removes) { + insertOffset++ + } + } +} + +function replaceRoot(oldRoot, newRoot) { + if (oldRoot && newRoot && oldRoot !== newRoot && oldRoot.parentNode) { + console.log(oldRoot) + oldRoot.parentNode.replaceChild(newRoot, oldRoot) + } + + return newRoot; +} + +},{"./apply-properties":17,"./create-element":18,"./update-widget":25,"vtree/is-widget":32,"vtree/vpatch":37}],24:[function(require,module,exports){ +var document = require("global/document") +var isArray = require("x-is-array") + +var domIndex = require("./dom-index") +var patchOp = require("./patch-op") +module.exports = patch + +function patch(rootNode, patches) { + return patchRecursive(rootNode, patches) +} + +function patchRecursive(rootNode, patches, renderOptions) { + var indices = patchIndices(patches) + + if (indices.length === 0) { + return rootNode + } + + var index = domIndex(rootNode, patches.a, indices) + var ownerDocument = rootNode.ownerDocument + + if (!renderOptions) { + renderOptions = { patch: patchRecursive } + if (ownerDocument !== document) { + renderOptions.document = ownerDocument + } + } + + for (var i = 0; i < indices.length; i++) { + var nodeIndex = indices[i] + rootNode = applyPatch(rootNode, + index[nodeIndex], + patches[nodeIndex], + renderOptions) + } + + return rootNode +} + +function applyPatch(rootNode, domNode, patchList, renderOptions) { + if (!domNode) { + return rootNode + } + + var newNode + + if (isArray(patchList)) { + for (var i = 0; i < patchList.length; i++) { + newNode = patchOp(patchList[i], domNode, renderOptions) + + if (domNode === rootNode) { + rootNode = newNode + } + } + } else { + newNode = patchOp(patchList, domNode, renderOptions) + + if (domNode === rootNode) { + rootNode = newNode + } + } + + return rootNode +} + +function patchIndices(patches) { + var indices = [] + + for (var key in patches) { + if (key !== "a") { + indices.push(Number(key)) + } + } + + return indices +} + +},{"./dom-index":19,"./patch-op":23,"global/document":20,"x-is-array":22}],25:[function(require,module,exports){ +var isWidget = require("vtree/is-widget") + +module.exports = updateWidget + +function updateWidget(a, b) { + if (isWidget(a) && isWidget(b)) { + if ("name" in a && "name" in b) { + return a.id === b.id + } else { + return a.init === b.init + } + } + + return false +} + +},{"vtree/is-widget":32}],26:[function(require,module,exports){ +var isArray = require("x-is-array") +var isObject = require("is-object") + +var VPatch = require("./vpatch") +var isVNode = require("./is-vnode") +var isVText = require("./is-vtext") +var isWidget = require("./is-widget") +var isThunk = require("./is-thunk") +var handleThunk = require("./handle-thunk") + +module.exports = diff + +function diff(a, b) { + var patch = { a: a } + walk(a, b, patch, 0) + return patch +} + +function walk(a, b, patch, index) { + if (a === b) { + if (isThunk(a) || isThunk(b)) { + thunks(a, b, patch, index) + } else { + hooks(b, patch, index) + } + return + } + + var apply = patch[index] + + if (b == null) { + apply = appendPatch(apply, new VPatch(VPatch.REMOVE, a, b)) + destroyWidgets(a, patch, index) + } else if (isThunk(a) || isThunk(b)) { + thunks(a, b, patch, index) + } else if (isVNode(b)) { + if (isVNode(a)) { + if (a.tagName === b.tagName && + a.namespace === b.namespace && + a.key === b.key) { + var propsPatch = diffProps(a.properties, b.properties, b.hooks) + if (propsPatch) { + apply = appendPatch(apply, + new VPatch(VPatch.PROPS, a, propsPatch)) + } + apply = diffChildren(a, b, patch, apply, index) + } else { + apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b)) + destroyWidgets(a, patch, index) + } + } else { + apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b)) + destroyWidgets(a, patch, index) + } + } else if (isVText(b)) { + if (!isVText(a)) { + apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b)) + destroyWidgets(a, patch, index) + } else if (a.text !== b.text) { + apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b)) + } + } else if (isWidget(b)) { + apply = appendPatch(apply, new VPatch(VPatch.WIDGET, a, b)) + + if (!isWidget(a)) { + destroyWidgets(a, patch, index) + } + } + + if (apply) { + patch[index] = apply + } +} + +function diffProps(a, b, hooks) { + var diff + + for (var aKey in a) { + if (!(aKey in b)) { + diff = diff || {} + diff[aKey] = undefined + } + + var aValue = a[aKey] + var bValue = b[aKey] + + if (hooks && aKey in hooks) { + diff = diff || {} + diff[aKey] = bValue + } else { + if (isObject(aValue) && isObject(bValue)) { + if (getPrototype(bValue) !== getPrototype(aValue)) { + diff = diff || {} + diff[aKey] = bValue + } else { + var objectDiff = diffProps(aValue, bValue) + if (objectDiff) { + diff = diff || {} + diff[aKey] = objectDiff + } + } + } else if (aValue !== bValue) { + diff = diff || {} + diff[aKey] = bValue + } + } + } + + for (var bKey in b) { + if (!(bKey in a)) { + diff = diff || {} + diff[bKey] = b[bKey] + } + } + + return diff +} + +function getPrototype(value) { + if (Object.getPrototypeOf) { + return Object.getPrototypeOf(value) + } else if (value.__proto__) { + return value.__proto__ + } else if (value.constructor) { + return value.constructor.prototype + } +} + +function diffChildren(a, b, patch, apply, index) { + var aChildren = a.children + var bChildren = reorder(aChildren, b.children) + + var aLen = aChildren.length + var bLen = bChildren.length + var len = aLen > bLen ? aLen : bLen + + for (var i = 0; i < len; i++) { + var leftNode = aChildren[i] + var rightNode = bChildren[i] + index += 1 + + if (!leftNode) { + if (rightNode) { + // Excess nodes in b need to be added + apply = appendPatch(apply, + new VPatch(VPatch.INSERT, null, rightNode)) + } + } else if (!rightNode) { + if (leftNode) { + // Excess nodes in a need to be removed + patch[index] = new VPatch(VPatch.REMOVE, leftNode, null) + destroyWidgets(leftNode, patch, index) + } + } else { + walk(leftNode, rightNode, patch, index) + } + + if (isVNode(leftNode) && leftNode.count) { + index += leftNode.count + } + } + + if (bChildren.moves) { + // Reorder nodes last + apply = appendPatch(apply, new VPatch(VPatch.ORDER, a, bChildren.moves)) + } + + return apply +} + +// Patch records for all destroyed widgets must be added because we need +// a DOM node reference for the destroy function +function destroyWidgets(vNode, patch, index) { + if (isWidget(vNode)) { + if (typeof vNode.destroy === "function") { + patch[index] = new VPatch(VPatch.REMOVE, vNode, null) + } + } else if (isVNode(vNode) && vNode.hasWidgets) { + var children = vNode.children + var len = children.length + for (var i = 0; i < len; i++) { + var child = children[i] + index += 1 + + destroyWidgets(child, patch, index) + + if (isVNode(child) && child.count) { + index += child.count + } + } + } +} + +// Create a sub-patch for thunks +function thunks(a, b, patch, index) { + var nodes = handleThunk(a, b); + var thunkPatch = diff(nodes.a, nodes.b) + if (hasPatches(thunkPatch)) { + patch[index] = new VPatch(VPatch.THUNK, null, thunkPatch) + } +} + +function hasPatches(patch) { + for (var index in patch) { + if (index !== "a") { + return true; + } + } + + return false; +} + +// Execute hooks when two nodes are identical +function hooks(vNode, patch, index) { + if (isVNode(vNode)) { + if (vNode.hooks) { + patch[index] = new VPatch(VPatch.PROPS, vNode.hooks, vNode.hooks) + } + + if (vNode.descendantHooks) { + var children = vNode.children + var len = children.length + for (var i = 0; i < len; i++) { + var child = children[i] + index += 1 + + hooks(child, patch, index) + + if (isVNode(child) && child.count) { + index += child.count + } + } + } + } +} + +// List diff, naive left to right reordering +function reorder(aChildren, bChildren) { + + var bKeys = keyIndex(bChildren) + + if (!bKeys) { + return bChildren + } + + var aKeys = keyIndex(aChildren) + + if (!aKeys) { + return bChildren + } + + var bMatch = {}, aMatch = {} + + for (var key in bKeys) { + bMatch[bKeys[key]] = aKeys[key] + } + + for (var key in aKeys) { + aMatch[aKeys[key]] = bKeys[key] + } + + var aLen = aChildren.length + var bLen = bChildren.length + var len = aLen > bLen ? aLen : bLen + var shuffle = [] + var freeIndex = 0 + var i = 0 + var moveIndex = 0 + var moves = {} + var removes = moves.removes = {} + var reverse = moves.reverse = {} + var hasMoves = false + + while (freeIndex < len) { + var move = aMatch[i] + if (move !== undefined) { + shuffle[i] = bChildren[move] + if (move !== moveIndex) { + moves[move] = moveIndex + reverse[moveIndex] = move + hasMoves = true + } + moveIndex++ + } else if (i in aMatch) { + shuffle[i] = undefined + removes[i] = moveIndex++ + hasMoves = true + } else { + while (bMatch[freeIndex] !== undefined) { + freeIndex++ + } + + if (freeIndex < len) { + var freeChild = bChildren[freeIndex] + if (freeChild) { + shuffle[i] = freeChild + if (freeIndex !== moveIndex) { + hasMoves = true + moves[freeIndex] = moveIndex + reverse[moveIndex] = freeIndex + } + moveIndex++ + } + freeIndex++ + } + } + i++ + } + + if (hasMoves) { + shuffle.moves = moves + } + + return shuffle +} + +function keyIndex(children) { + var i, keys + + for (i = 0; i < children.length; i++) { + var child = children[i] + + if (child.key !== undefined) { + keys = keys || {} + keys[child.key] = i + } + } + + return keys +} + +function appendPatch(apply, patch) { + if (apply) { + if (isArray(apply)) { + apply.push(patch) + } else { + apply = [apply, patch] + } + + return apply + } else { + return patch + } +} + +},{"./handle-thunk":27,"./is-thunk":28,"./is-vnode":30,"./is-vtext":31,"./is-widget":32,"./vpatch":37,"is-object":33,"x-is-array":34}],27:[function(require,module,exports){ +var isVNode = require("./is-vnode") +var isVText = require("./is-vtext") +var isWidget = require("./is-widget") +var isThunk = require("./is-thunk") + +module.exports = handleThunk + +function handleThunk(a, b) { + var renderedA = a + var renderedB = b + + if (isThunk(b)) { + renderedB = renderThunk(b, a) + } + + if (isThunk(a)) { + renderedA = renderThunk(a, null) + } + + return { + a: renderedA, + b: renderedB + } +} + +function renderThunk(thunk, previous) { + var renderedThunk = thunk.vnode + + if (!renderedThunk) { + renderedThunk = thunk.vnode = thunk.render(previous) + } + + if (!(isVNode(renderedThunk) || + isVText(renderedThunk) || + isWidget(renderedThunk))) { + throw new Error("thunk did not return a valid node"); + } + + return renderedThunk +} + +},{"./is-thunk":28,"./is-vnode":30,"./is-vtext":31,"./is-widget":32}],28:[function(require,module,exports){ +module.exports = isThunk + +function isThunk(t) { + return t && t.type === "Thunk" +} + +},{}],29:[function(require,module,exports){ +module.exports = isHook + +function isHook(hook) { + return hook && typeof hook.hook === "function" && + !hook.hasOwnProperty("hook") +} + +},{}],30:[function(require,module,exports){ +var version = require("./version") + +module.exports = isVirtualNode + +function isVirtualNode(x) { + return x && x.type === "VirtualNode" && x.version === version +} + +},{"./version":35}],31:[function(require,module,exports){ +var version = require("./version") + +module.exports = isVirtualText + +function isVirtualText(x) { + return x && x.type === "VirtualText" && x.version === version +} + +},{"./version":35}],32:[function(require,module,exports){ +module.exports = isWidget + +function isWidget(w) { + return w && w.type === "Widget" +} + +},{}],33:[function(require,module,exports){ +module.exports=require(21) +},{}],34:[function(require,module,exports){ +module.exports=require(22) +},{}],35:[function(require,module,exports){ +module.exports = "1" + +},{}],36:[function(require,module,exports){ +var version = require("./version") +var isVNode = require("./is-vnode") +var isWidget = require("./is-widget") +var isVHook = require("./is-vhook") + +module.exports = VirtualNode + +var noProperties = {} +var noChildren = [] + +function VirtualNode(tagName, properties, children, key, namespace) { + this.tagName = tagName + this.properties = properties || noProperties + this.children = children || noChildren + this.key = key != null ? String(key) : undefined + this.namespace = (typeof namespace === "string") ? namespace : null + + var count = (children && children.length) || 0 + var descendants = 0 + var hasWidgets = false + var descendantHooks = false + var hooks + + for (var propName in properties) { + if (properties.hasOwnProperty(propName)) { + var property = properties[propName] + if (isVHook(property)) { + if (!hooks) { + hooks = {} + } + + hooks[propName] = property + } + } + } + + for (var i = 0; i < count; i++) { + var child = children[i] + if (isVNode(child)) { + descendants += child.count || 0 + + if (!hasWidgets && child.hasWidgets) { + hasWidgets = true + } + + if (!descendantHooks && (child.hooks || child.descendantHooks)) { + descendantHooks = true + } + } else if (!hasWidgets && isWidget(child)) { + if (typeof child.destroy === "function") { + hasWidgets = true + } + } + } + + this.count = count + descendants + this.hasWidgets = hasWidgets + this.hooks = hooks + this.descendantHooks = descendantHooks +} + +VirtualNode.prototype.version = version +VirtualNode.prototype.type = "VirtualNode" + +},{"./is-vhook":29,"./is-vnode":30,"./is-widget":32,"./version":35}],37:[function(require,module,exports){ +var version = require("./version") + +VirtualPatch.NONE = 0 +VirtualPatch.VTEXT = 1 +VirtualPatch.VNODE = 2 +VirtualPatch.WIDGET = 3 +VirtualPatch.PROPS = 4 +VirtualPatch.ORDER = 5 +VirtualPatch.INSERT = 6 +VirtualPatch.REMOVE = 7 +VirtualPatch.THUNK = 8 + +module.exports = VirtualPatch + +function VirtualPatch(type, vNode, patch) { + this.type = Number(type) + this.vNode = vNode + this.patch = patch +} + +VirtualPatch.prototype.version = version +VirtualPatch.prototype.type = "VirtualPatch" + +},{"./version":35}],38:[function(require,module,exports){ +var version = require("./version") + +module.exports = VirtualText + +function VirtualText(text) { + this.text = String(text) +} + +VirtualText.prototype.version = version +VirtualText.prototype.type = "VirtualText" + +},{"./version":35}],39:[function(require,module,exports){ + +var VNode = require('vtree/vnode'); +var VText = require('vtree/vtext'); +var diff = require('vtree/diff'); +var patch = require('vdom/patch'); +var createElement = require('vdom/create-element'); +var DataSet = require("data-set"); +var Delegator = require("dom-delegator"); +var isHook = require("vtree/is-vhook"); + +Elm.Native.VirtualDom = {}; +Elm.Native.VirtualDom.make = function(elm) +{ + elm.Native = elm.Native || {}; + elm.Native.VirtualDom = elm.Native.VirtualDom || {}; + if (elm.Native.VirtualDom.values) + { + return elm.Native.VirtualDom.values; + } + + // This manages event listeners. Somehow... + // Save a reference for use in on(...) + var delegator = Delegator(); + + var Element = Elm.Native.Graphics.Element.make(elm); + var Json = Elm.Native.Json.make(elm); + var List = Elm.Native.List.make(elm); + var Signal = Elm.Native.Signal.make(elm); + var Utils = Elm.Native.Utils.make(elm); + + var ATTRIBUTE_KEY = 'UniqueNameThatOthersAreVeryUnlikelyToUse'; + + function listToProperties(list) + { + var object = {}; + while (list.ctor !== '[]') + { + var entry = list._0; + if (entry.key === ATTRIBUTE_KEY) + { + object.attributes = object.attributes || {}; + object.attributes[entry.value.attrKey] = entry.value.attrValue; + } + else + { + object[entry.key] = entry.value; + } + list = list._1; + } + return object; + } + + function node(name, propertyList, contents) + { + var props = listToProperties(propertyList); + + var key, namespace; + // support keys + if (props.key !== undefined) + { + key = props.key; + props.key = undefined; + } + + // support namespace + if (props.namespace !== undefined) + { + namespace = props.namespace; + props.namespace = undefined; + } + + // ensure that setting text of an input does not move the cursor + var useSoftSet = + name === 'input' + && props.value !== undefined + && !isHook(props.value); + + if (useSoftSet) + { + props.value = SoftSetHook(props.value); + } + + return new VNode(name, props, List.toArray(contents), key, namespace); + } + + function property(key, value) + { + return { + key: key, + value: value + }; + } + + function attribute(key, value) + { + return { + key: ATTRIBUTE_KEY, + value: { + attrKey: key, + attrValue: value + } + }; + } + + function on(name, decoder, createMessage) + { + // Ensure we're listening for this type of event + delegator.listenTo(name); + function eventHandler(event) + { + var value = A2(Json.runDecoderValue, decoder, event); + if (value.ctor === 'Ok') + { + Signal.sendMessage(createMessage(value._0)); + } + } + return property(name, DataSetHook(eventHandler)); + } + + function DataSetHook(value) + { + if (!(this instanceof DataSetHook)) + { + return new DataSetHook(value); + } + + this.value = value; + } + + DataSetHook.prototype.hook = function (node, propertyName) { + var ds = DataSet(node); + ds[propertyName] = this.value; + }; + + + function SoftSetHook(value) + { + if (!(this instanceof SoftSetHook)) + { + return new SoftSetHook(value); + } + + this.value = value; + } + + SoftSetHook.prototype.hook = function (node, propertyName) + { + if (node[propertyName] !== this.value) + { + node[propertyName] = this.value; + } + }; + + function text(string) + { + return new VText(string); + } + + function fromElement(element) + { + return { + type: "Widget", + + element: element, + + init: function () { + return Element.render(element); + }, + + update: function (previous, node) { + return Element.update(node, previous.element, element); + } + }; + } + + function toElement(width, height, html) + { + return A3(Element.newElement, width, height, { + ctor: 'Custom', + type: 'evancz/elm-html', + render: render, + update: update, + model: html + }); + } + + function render(model) + { + var element = Element.createNode('div'); + element.appendChild(createElement(model)); + return element; + } + + function update(node, oldModel, newModel) + { + updateAndReplace(node.firstChild, oldModel, newModel); + return node; + } + + function updateAndReplace(node, oldModel, newModel) + { + var patches = diff(oldModel, newModel); + var newNode = patch(node, patches); + return newNode; + } + + function lazyRef(fn, a) + { + function thunk() + { + return fn(a); + } + return new Thunk(fn, [a], thunk); + } + + function lazyRef2(fn, a, b) + { + function thunk() + { + return A2(fn, a, b); + } + return new Thunk(fn, [a,b], thunk); + } + + function lazyRef3(fn, a, b, c) + { + function thunk() + { + return A3(fn, a, b, c); + } + return new Thunk(fn, [a,b,c], thunk); + } + + function Thunk(fn, args, thunk) + { + this.fn = fn; + this.args = args; + this.vnode = null; + this.key = undefined; + this.thunk = thunk; + } + + Thunk.prototype.type = "Thunk"; + Thunk.prototype.update = updateThunk; + Thunk.prototype.render = renderThunk; + + function shouldUpdate(current, previous) + { + if (current.fn !== previous.fn) + { + return true; + } + + // if it's the same function, we know the number of args must match + var cargs = current.args; + var pargs = previous.args; + + for (var i = cargs.length; i--; ) + { + if (cargs[i] !== pargs[i]) + { + return true; + } + } + + return false; + } + + function updateThunk(previous, domNode) + { + if (!shouldUpdate(this, previous)) + { + this.vnode = previous.vnode; + return; + } + + if (!this.vnode) + { + this.vnode = this.thunk(); + } + + var patches = diff(previous.vnode, this.vnode); + patch(domNode, patches); + } + + function renderThunk() + { + return this.thunk(); + } + + return Elm.Native.VirtualDom.values = { + node: F3(node), + text: text, + on: F3(on), + + property: F2(property), + attribute: F2(attribute), + + lazy: F2(lazyRef), + lazy2: F3(lazyRef2), + lazy3: F4(lazyRef3), + + toElement: F3(toElement), + fromElement: fromElement, + + render: createElement, + updateAndReplace: updateAndReplace + }; +}; + +},{"data-set":2,"dom-delegator":8,"vdom/create-element":18,"vdom/patch":24,"vtree/diff":26,"vtree/is-vhook":29,"vtree/vnode":36,"vtree/vtext":38}],40:[function(require,module,exports){ + +},{}]},{},[39]); + +Elm.Native = Elm.Native || {}; +Elm.Native.Window = {}; +Elm.Native.Window.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Window = localRuntime.Native.Window || {}; + if (localRuntime.Native.Window.values) + { + return localRuntime.Native.Window.values; + } + + var NS = Elm.Native.Signal.make(localRuntime); + var Tuple2 = Elm.Native.Utils.make(localRuntime).Tuple2; + + + function getWidth() + { + return localRuntime.node.clientWidth; + } + + + function getHeight() + { + if (localRuntime.isFullscreen()) + { + return window.innerHeight; + } + return localRuntime.node.clientHeight; + } + + + var dimensions = NS.input('Window.dimensions', Tuple2(getWidth(), getHeight())); + + + function resizeIfNeeded() + { + // Do not trigger event if the dimensions have not changed. + // This should be most of the time. + var w = getWidth(); + var h = getHeight(); + if (dimensions.value._0 === w && dimensions.value._1 === h) + { + return; + } + + setTimeout(function () { + // Check again to see if the dimensions have changed. + // It is conceivable that the dimensions have changed + // again while some other event was being processed. + var w = getWidth(); + var h = getHeight(); + if (dimensions.value._0 === w && dimensions.value._1 === h) + { + return; + } + localRuntime.notify(dimensions.id, Tuple2(w,h)); + }, 0); + } + + + localRuntime.addListener([dimensions.id], window, 'resize', resizeIfNeeded); + + + return localRuntime.Native.Window.values = { + dimensions: dimensions, + resizeIfNeeded: resizeIfNeeded + }; +}; + +Elm.Result = Elm.Result || {}; +Elm.Result.make = function (_elm) { + "use strict"; + _elm.Result = _elm.Result || {}; + if (_elm.Result.values) + return _elm.Result.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Result", + $Maybe = Elm.Maybe.make(_elm); + var toMaybe = function (result) { + return function () { + switch (result.ctor) + {case "Err": + return $Maybe.Nothing; + case "Ok": + return $Maybe.Just(result._0);} + _U.badCase($moduleName, + "between lines 164 and 177"); + }(); + }; + var Err = function (a) { + return {ctor: "Err",_0: a}; + }; + var andThen = F2(function (result, + callback) { + return function () { + switch (result.ctor) + {case "Err": + return Err(result._0); + case "Ok": + return callback(result._0);} + _U.badCase($moduleName, + "between lines 126 and 145"); + }(); + }); + var Ok = function (a) { + return {ctor: "Ok",_0: a}; + }; + var map = F2(function (func, + ra) { + return function () { + switch (ra.ctor) + {case "Err": return Err(ra._0); + case "Ok": + return Ok(func(ra._0));} + _U.badCase($moduleName, + "between lines 41 and 52"); + }(); + }); + var map2 = F3(function (func, + ra, + rb) { + return function () { + var _v9 = {ctor: "_Tuple2" + ,_0: ra + ,_1: rb}; + switch (_v9.ctor) + {case "_Tuple2": + switch (_v9._0.ctor) + {case "Err": + return Err(_v9._0._0); + case "Ok": switch (_v9._1.ctor) + {case "Ok": return Ok(A2(func, + _v9._0._0, + _v9._1._0));} + break;} + switch (_v9._1.ctor) + {case "Err": + return Err(_v9._1._0);} + break;} + _U.badCase($moduleName, + "between lines 55 and 58"); + }(); + }); + var map3 = F4(function (func, + ra, + rb, + rc) { + return function () { + var _v16 = {ctor: "_Tuple3" + ,_0: ra + ,_1: rb + ,_2: rc}; + switch (_v16.ctor) + {case "_Tuple3": + switch (_v16._0.ctor) + {case "Err": + return Err(_v16._0._0); + case "Ok": switch (_v16._1.ctor) + {case "Ok": + switch (_v16._2.ctor) + {case "Ok": return Ok(A3(func, + _v16._0._0, + _v16._1._0, + _v16._2._0));} + break;} + break;} + switch (_v16._1.ctor) + {case "Err": + return Err(_v16._1._0);} + switch (_v16._2.ctor) + {case "Err": + return Err(_v16._2._0);} + break;} + _U.badCase($moduleName, + "between lines 63 and 67"); + }(); + }); + var map4 = F5(function (func, + ra, + rb, + rc, + rd) { + return function () { + var _v26 = {ctor: "_Tuple4" + ,_0: ra + ,_1: rb + ,_2: rc + ,_3: rd}; + switch (_v26.ctor) + {case "_Tuple4": + switch (_v26._0.ctor) + {case "Err": + return Err(_v26._0._0); + case "Ok": switch (_v26._1.ctor) + {case "Ok": + switch (_v26._2.ctor) + {case "Ok": + switch (_v26._3.ctor) + {case "Ok": return Ok(A4(func, + _v26._0._0, + _v26._1._0, + _v26._2._0, + _v26._3._0));} + break;} + break;} + break;} + switch (_v26._1.ctor) + {case "Err": + return Err(_v26._1._0);} + switch (_v26._2.ctor) + {case "Err": + return Err(_v26._2._0);} + switch (_v26._3.ctor) + {case "Err": + return Err(_v26._3._0);} + break;} + _U.badCase($moduleName, + "between lines 72 and 77"); + }(); + }); + var map5 = F6(function (func, + ra, + rb, + rc, + rd, + re) { + return function () { + var _v39 = {ctor: "_Tuple5" + ,_0: ra + ,_1: rb + ,_2: rc + ,_3: rd + ,_4: re}; + switch (_v39.ctor) + {case "_Tuple5": + switch (_v39._0.ctor) + {case "Err": + return Err(_v39._0._0); + case "Ok": switch (_v39._1.ctor) + {case "Ok": + switch (_v39._2.ctor) + {case "Ok": + switch (_v39._3.ctor) + {case "Ok": + switch (_v39._4.ctor) + {case "Ok": return Ok(A5(func, + _v39._0._0, + _v39._1._0, + _v39._2._0, + _v39._3._0, + _v39._4._0));} + break;} + break;} + break;} + break;} + switch (_v39._1.ctor) + {case "Err": + return Err(_v39._1._0);} + switch (_v39._2.ctor) + {case "Err": + return Err(_v39._2._0);} + switch (_v39._3.ctor) + {case "Err": + return Err(_v39._3._0);} + switch (_v39._4.ctor) + {case "Err": + return Err(_v39._4._0);} + break;} + _U.badCase($moduleName, + "between lines 82 and 123"); + }(); + }); + var formatError = F2(function (f, + result) { + return function () { + switch (result.ctor) + {case "Err": + return Err(f(result._0)); + case "Ok": + return Ok(result._0);} + _U.badCase($moduleName, + "between lines 148 and 161"); + }(); + }); + var fromMaybe = F2(function (err, + maybe) { + return function () { + switch (maybe.ctor) + {case "Just": + return Ok(maybe._0); + case "Nothing": + return Err(err);} + _U.badCase($moduleName, + "between lines 180 and 182"); + }(); + }); + _elm.Result.values = {_op: _op + ,map: map + ,map2: map2 + ,map3: map3 + ,map4: map4 + ,map5: map5 + ,andThen: andThen + ,toMaybe: toMaybe + ,fromMaybe: fromMaybe + ,formatError: formatError + ,Ok: Ok + ,Err: Err}; + return _elm.Result.values; +}; +Elm.Sha = Elm.Sha || {}; +Elm.Sha.make = function (_elm) { + "use strict"; + _elm.Sha = _elm.Sha || {}; + if (_elm.Sha.values) + return _elm.Sha.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Sha", + $Native$Sha = Elm.Native.Sha.make(_elm); + var digest = $Native$Sha.digest; + var update = $Native$Sha.update; + var createHash = $Native$Sha.createHash; + var Hash = {ctor: "Hash"}; + _elm.Sha.values = {_op: _op + ,Hash: Hash + ,createHash: createHash + ,update: update + ,digest: digest}; + return _elm.Sha.values; +}; +Elm.Signal = Elm.Signal || {}; +Elm.Signal.make = function (_elm) { + "use strict"; + _elm.Signal = _elm.Signal || {}; + if (_elm.Signal.values) + return _elm.Signal.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Signal", + $Basics = Elm.Basics.make(_elm), + $Debug = Elm.Debug.make(_elm), + $List = Elm.List.make(_elm), + $Maybe = Elm.Maybe.make(_elm), + $Native$Signal = Elm.Native.Signal.make(_elm), + $Task = Elm.Task.make(_elm); + var send = F2(function (_v0, + value) { + return function () { + switch (_v0.ctor) + {case "Address": + return A2($Task.onError, + _v0._0(value), + function (_v3) { + return function () { + return $Task.succeed({ctor: "_Tuple0"}); + }(); + });} + _U.badCase($moduleName, + "between lines 370 and 371"); + }(); + }); + var Message = function (a) { + return {ctor: "Message" + ,_0: a}; + }; + var message = F2(function (_v5, + value) { + return function () { + switch (_v5.ctor) + {case "Address": + return Message(_v5._0(value));} + _U.badCase($moduleName, + "on line 352, column 5 to 24"); + }(); + }); + var mailbox = $Native$Signal.mailbox; + var Address = function (a) { + return {ctor: "Address" + ,_0: a}; + }; + var forwardTo = F2(function (_v8, + f) { + return function () { + switch (_v8.ctor) + {case "Address": + return Address(function (x) { + return _v8._0(f(x)); + });} + _U.badCase($moduleName, + "on line 339, column 5 to 29"); + }(); + }); + var Mailbox = F2(function (a, + b) { + return {_: {} + ,address: a + ,signal: b}; + }); + var sampleOn = $Native$Signal.sampleOn; + var dropRepeats = $Native$Signal.dropRepeats; + var filterMap = $Native$Signal.filterMap; + var filter = F3(function (isOk, + base, + signal) { + return A3(filterMap, + function (value) { + return isOk(value) ? $Maybe.Just(value) : $Maybe.Nothing; + }, + base, + signal); + }); + var merge = F2(function (left, + right) { + return A3($Native$Signal.genericMerge, + $Basics.always, + left, + right); + }); + var mergeMany = function (signalList) { + return function () { + var _v11 = $List.reverse(signalList); + switch (_v11.ctor) + {case "::": + return A3($List.foldl, + merge, + _v11._0, + _v11._1); + case "[]": + return $Debug.crash("mergeMany was given an empty list!");} + _U.badCase($moduleName, + "between lines 177 and 197"); + }(); + }; + var foldp = $Native$Signal.foldp; + var map5 = $Native$Signal.map5; + var map4 = $Native$Signal.map4; + var map3 = $Native$Signal.map3; + var map2 = $Native$Signal.map2; + _op["~"] = F2(function (funcs, + args) { + return A3(map2, + F2(function (f,v) { + return f(v); + }), + funcs, + args); + }); + var map = $Native$Signal.map; + _op["<~"] = map; + var constant = $Native$Signal.constant; + var Signal = {ctor: "Signal"}; + _elm.Signal.values = {_op: _op + ,merge: merge + ,mergeMany: mergeMany + ,map: map + ,map2: map2 + ,map3: map3 + ,map4: map4 + ,map5: map5 + ,constant: constant + ,dropRepeats: dropRepeats + ,filter: filter + ,filterMap: filterMap + ,sampleOn: sampleOn + ,foldp: foldp + ,mailbox: mailbox + ,send: send + ,message: message + ,forwardTo: forwardTo + ,Mailbox: Mailbox}; + return _elm.Signal.values; +}; +Elm.String = Elm.String || {}; +Elm.String.make = function (_elm) { + "use strict"; + _elm.String = _elm.String || {}; + if (_elm.String.values) + return _elm.String.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "String", + $Maybe = Elm.Maybe.make(_elm), + $Native$String = Elm.Native.String.make(_elm), + $Result = Elm.Result.make(_elm); + var fromList = $Native$String.fromList; + var toList = $Native$String.toList; + var toFloat = $Native$String.toFloat; + var toInt = $Native$String.toInt; + var indices = $Native$String.indexes; + var indexes = $Native$String.indexes; + var endsWith = $Native$String.endsWith; + var startsWith = $Native$String.startsWith; + var contains = $Native$String.contains; + var all = $Native$String.all; + var any = $Native$String.any; + var toLower = $Native$String.toLower; + var toUpper = $Native$String.toUpper; + var lines = $Native$String.lines; + var words = $Native$String.words; + var trimRight = $Native$String.trimRight; + var trimLeft = $Native$String.trimLeft; + var trim = $Native$String.trim; + var padRight = $Native$String.padRight; + var padLeft = $Native$String.padLeft; + var pad = $Native$String.pad; + var dropRight = $Native$String.dropRight; + var dropLeft = $Native$String.dropLeft; + var right = $Native$String.right; + var left = $Native$String.left; + var slice = $Native$String.slice; + var repeat = $Native$String.repeat; + var join = $Native$String.join; + var split = $Native$String.split; + var foldr = $Native$String.foldr; + var foldl = $Native$String.foldl; + var reverse = $Native$String.reverse; + var filter = $Native$String.filter; + var map = $Native$String.map; + var length = $Native$String.length; + var concat = $Native$String.concat; + var append = $Native$String.append; + var uncons = $Native$String.uncons; + var cons = $Native$String.cons; + var fromChar = function ($char) { + return A2(cons,$char,""); + }; + var isEmpty = $Native$String.isEmpty; + _elm.String.values = {_op: _op + ,isEmpty: isEmpty + ,length: length + ,reverse: reverse + ,repeat: repeat + ,cons: cons + ,uncons: uncons + ,fromChar: fromChar + ,append: append + ,concat: concat + ,split: split + ,join: join + ,words: words + ,lines: lines + ,slice: slice + ,left: left + ,right: right + ,dropLeft: dropLeft + ,dropRight: dropRight + ,contains: contains + ,startsWith: startsWith + ,endsWith: endsWith + ,indexes: indexes + ,indices: indices + ,toInt: toInt + ,toFloat: toFloat + ,toList: toList + ,fromList: fromList + ,toUpper: toUpper + ,toLower: toLower + ,pad: pad + ,padLeft: padLeft + ,padRight: padRight + ,trim: trim + ,trimLeft: trimLeft + ,trimRight: trimRight + ,map: map + ,filter: filter + ,foldl: foldl + ,foldr: foldr + ,any: any + ,all: all}; + return _elm.String.values; +}; +Elm.Task = Elm.Task || {}; +Elm.Task.make = function (_elm) { + "use strict"; + _elm.Task = _elm.Task || {}; + if (_elm.Task.values) + return _elm.Task.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Task", + $List = Elm.List.make(_elm), + $Maybe = Elm.Maybe.make(_elm), + $Native$Task = Elm.Native.Task.make(_elm), + $Result = Elm.Result.make(_elm); + var sleep = $Native$Task.sleep; + var spawn = $Native$Task.spawn; + var ThreadID = function (a) { + return {ctor: "ThreadID" + ,_0: a}; + }; + var onError = $Native$Task.catch_; + var andThen = $Native$Task.andThen; + var fail = $Native$Task.fail; + var mapError = F2(function (f, + promise) { + return A2(onError, + promise, + function (err) { + return fail(f(err)); + }); + }); + var succeed = $Native$Task.succeed; + var map = F2(function (func, + promiseA) { + return A2(andThen, + promiseA, + function (a) { + return succeed(func(a)); + }); + }); + var map2 = F3(function (func, + promiseA, + promiseB) { + return A2(andThen, + promiseA, + function (a) { + return A2(andThen, + promiseB, + function (b) { + return succeed(A2(func,a,b)); + }); + }); + }); + var map3 = F4(function (func, + promiseA, + promiseB, + promiseC) { + return A2(andThen, + promiseA, + function (a) { + return A2(andThen, + promiseB, + function (b) { + return A2(andThen, + promiseC, + function (c) { + return succeed(A3(func, + a, + b, + c)); + }); + }); + }); + }); + var map4 = F5(function (func, + promiseA, + promiseB, + promiseC, + promiseD) { + return A2(andThen, + promiseA, + function (a) { + return A2(andThen, + promiseB, + function (b) { + return A2(andThen, + promiseC, + function (c) { + return A2(andThen, + promiseD, + function (d) { + return succeed(A4(func, + a, + b, + c, + d)); + }); + }); + }); + }); + }); + var map5 = F6(function (func, + promiseA, + promiseB, + promiseC, + promiseD, + promiseE) { + return A2(andThen, + promiseA, + function (a) { + return A2(andThen, + promiseB, + function (b) { + return A2(andThen, + promiseC, + function (c) { + return A2(andThen, + promiseD, + function (d) { + return A2(andThen, + promiseE, + function (e) { + return succeed(A5(func, + a, + b, + c, + d, + e)); + }); + }); + }); + }); + }); + }); + var andMap = F2(function (promiseFunc, + promiseValue) { + return A2(andThen, + promiseFunc, + function (func) { + return A2(andThen, + promiseValue, + function (value) { + return succeed(func(value)); + }); + }); + }); + var sequence = function (promises) { + return function () { + switch (promises.ctor) + {case "::": return A3(map2, + F2(function (x,y) { + return A2($List._op["::"], + x, + y); + }), + promises._0, + sequence(promises._1)); + case "[]": + return succeed(_L.fromArray([]));} + _U.badCase($moduleName, + "between lines 101 and 106"); + }(); + }; + var toMaybe = function (task) { + return A2(onError, + A2(map,$Maybe.Just,task), + function (_v3) { + return function () { + return succeed($Maybe.Nothing); + }(); + }); + }; + var fromMaybe = F2(function ($default, + maybe) { + return function () { + switch (maybe.ctor) + {case "Just": + return succeed(maybe._0); + case "Nothing": + return fail($default);} + _U.badCase($moduleName, + "between lines 139 and 141"); + }(); + }); + var toResult = function (task) { + return A2(onError, + A2(map,$Result.Ok,task), + function (msg) { + return succeed($Result.Err(msg)); + }); + }; + var fromResult = function (result) { + return function () { + switch (result.ctor) + {case "Err": + return fail(result._0); + case "Ok": + return succeed(result._0);} + _U.badCase($moduleName, + "between lines 151 and 153"); + }(); + }; + var Task = {ctor: "Task"}; + _elm.Task.values = {_op: _op + ,succeed: succeed + ,fail: fail + ,map: map + ,map2: map2 + ,map3: map3 + ,map4: map4 + ,map5: map5 + ,andMap: andMap + ,sequence: sequence + ,andThen: andThen + ,onError: onError + ,mapError: mapError + ,toMaybe: toMaybe + ,fromMaybe: fromMaybe + ,toResult: toResult + ,fromResult: fromResult + ,spawn: spawn + ,sleep: sleep}; + return _elm.Task.values; +}; +Elm.Text = Elm.Text || {}; +Elm.Text.make = function (_elm) { + "use strict"; + _elm.Text = _elm.Text || {}; + if (_elm.Text.values) + return _elm.Text.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Text", + $Color = Elm.Color.make(_elm), + $List = Elm.List.make(_elm), + $Maybe = Elm.Maybe.make(_elm), + $Native$Text = Elm.Native.Text.make(_elm); + var line = $Native$Text.line; + var italic = $Native$Text.italic; + var bold = $Native$Text.bold; + var color = $Native$Text.color; + var height = $Native$Text.height; + var link = $Native$Text.link; + var monospace = $Native$Text.monospace; + var typeface = $Native$Text.typeface; + var style = $Native$Text.style; + var append = $Native$Text.append; + var fromString = $Native$Text.fromString; + var empty = fromString(""); + var concat = function (texts) { + return A3($List.foldr, + append, + empty, + texts); + }; + var join = F2(function (seperator, + texts) { + return concat(A2($List.intersperse, + seperator, + texts)); + }); + var defaultStyle = {_: {} + ,bold: false + ,color: $Color.black + ,height: $Maybe.Nothing + ,italic: false + ,line: $Maybe.Nothing + ,typeface: _L.fromArray([])}; + var Style = F6(function (a, + b, + c, + d, + e, + f) { + return {_: {} + ,bold: d + ,color: c + ,height: b + ,italic: e + ,line: f + ,typeface: a}; + }); + var Through = {ctor: "Through"}; + var Over = {ctor: "Over"}; + var Under = {ctor: "Under"}; + var Text = {ctor: "Text"}; + _elm.Text.values = {_op: _op + ,fromString: fromString + ,empty: empty + ,append: append + ,concat: concat + ,join: join + ,link: link + ,style: style + ,defaultStyle: defaultStyle + ,typeface: typeface + ,monospace: monospace + ,height: height + ,color: color + ,bold: bold + ,italic: italic + ,line: line + ,Style: Style + ,Under: Under + ,Over: Over + ,Through: Through}; + return _elm.Text.values; +}; +Elm.Transform2D = Elm.Transform2D || {}; +Elm.Transform2D.make = function (_elm) { + "use strict"; + _elm.Transform2D = _elm.Transform2D || {}; + if (_elm.Transform2D.values) + return _elm.Transform2D.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Transform2D", + $Native$Transform2D = Elm.Native.Transform2D.make(_elm); + var multiply = $Native$Transform2D.multiply; + var rotation = $Native$Transform2D.rotation; + var matrix = $Native$Transform2D.matrix; + var translation = F2(function (x, + y) { + return A6(matrix, + 1, + 0, + 0, + 1, + x, + y); + }); + var scale = function (s) { + return A6(matrix, + s, + 0, + 0, + s, + 0, + 0); + }; + var scaleX = function (x) { + return A6(matrix, + x, + 0, + 0, + 1, + 0, + 0); + }; + var scaleY = function (y) { + return A6(matrix, + 1, + 0, + 0, + y, + 0, + 0); + }; + var identity = $Native$Transform2D.identity; + var Transform2D = {ctor: "Transform2D"}; + _elm.Transform2D.values = {_op: _op + ,identity: identity + ,matrix: matrix + ,multiply: multiply + ,rotation: rotation + ,translation: translation + ,scale: scale + ,scaleX: scaleX + ,scaleY: scaleY}; + return _elm.Transform2D.values; +}; +Elm.VirtualDom = Elm.VirtualDom || {}; +Elm.VirtualDom.make = function (_elm) { + "use strict"; + _elm.VirtualDom = _elm.VirtualDom || {}; + if (_elm.VirtualDom.values) + return _elm.VirtualDom.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "VirtualDom", + $Graphics$Element = Elm.Graphics.Element.make(_elm), + $Json$Decode = Elm.Json.Decode.make(_elm), + $Native$VirtualDom = Elm.Native.VirtualDom.make(_elm), + $Signal = Elm.Signal.make(_elm); + var lazy3 = $Native$VirtualDom.lazy3; + var lazy2 = $Native$VirtualDom.lazy2; + var lazy = $Native$VirtualDom.lazy; + var on = $Native$VirtualDom.on; + var attribute = $Native$VirtualDom.attribute; + var property = $Native$VirtualDom.property; + var Property = {ctor: "Property"}; + var fromElement = $Native$VirtualDom.fromElement; + var toElement = $Native$VirtualDom.toElement; + var text = $Native$VirtualDom.text; + var node = $Native$VirtualDom.node; + var Node = {ctor: "Node"}; + _elm.VirtualDom.values = {_op: _op + ,Node: Node + ,node: node + ,text: text + ,toElement: toElement + ,fromElement: fromElement + ,Property: Property + ,property: property + ,attribute: attribute + ,on: on + ,lazy: lazy + ,lazy2: lazy2 + ,lazy3: lazy3}; + return _elm.VirtualDom.values; +}; +Elm.Window = Elm.Window || {}; +Elm.Window.make = function (_elm) { + "use strict"; + _elm.Window = _elm.Window || {}; + if (_elm.Window.values) + return _elm.Window.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "Window", + $Basics = Elm.Basics.make(_elm), + $Native$Window = Elm.Native.Window.make(_elm), + $Signal = Elm.Signal.make(_elm); + var dimensions = $Native$Window.dimensions; + var width = A2($Signal.map, + $Basics.fst, + dimensions); + var height = A2($Signal.map, + $Basics.snd, + dimensions); + _elm.Window.values = {_op: _op + ,dimensions: dimensions + ,width: width + ,height: height}; + return _elm.Window.values; +}; +Elm.YPassword = Elm.YPassword || {}; +Elm.YPassword.make = function (_elm) { + "use strict"; + _elm.YPassword = _elm.YPassword || {}; + if (_elm.YPassword.values) + return _elm.YPassword.values; + var _op = {}, + _N = Elm.Native, + _U = _N.Utils.make(_elm), + _L = _N.List.make(_elm), + $moduleName = "YPassword", + $Basics = Elm.Basics.make(_elm), + $Color = Elm.Color.make(_elm), + $Graphics$Element = Elm.Graphics.Element.make(_elm), + $Graphics$Input = Elm.Graphics.Input.make(_elm), + $Graphics$Input$Field = Elm.Graphics.Input.Field.make(_elm), + $Markdown = Elm.Markdown.make(_elm), + $Maybe = Elm.Maybe.make(_elm), + $Sha = Elm.Sha.make(_elm), + $Signal = Elm.Signal.make(_elm), + $String = Elm.String.make(_elm), + $Text = Elm.Text.make(_elm), + $Window = Elm.Window.make(_elm); + var yStyle = function (n) { + return function () { + var ystyle = {_: {} + ,bold: false + ,color: $Color.charcoal + ,height: $Maybe.Just(n) + ,italic: false + ,line: $Maybe.Nothing + ,typeface: _L.fromArray(["Futura" + ,"sans-serif"])}; + var f = $Graphics$Input$Field.defaultStyle; + return _U.replace([["style" + ,ystyle]], + f); + }(); + }; + var masterContent = $Signal.mailbox($Graphics$Input$Field.noContent); + var domainContent = $Signal.mailbox($Graphics$Input$Field.noContent); + var nbMailbox = $Signal.mailbox(0); + var nbDropdown = A2($Graphics$Input.dropDown, + $Signal.message(nbMailbox.address), + _L.fromArray([{ctor: "_Tuple2" + ,_0: "0" + ,_1: 0} + ,{ctor: "_Tuple2",_0: "1",_1: 1} + ,{ctor: "_Tuple2",_0: "2",_1: 2} + ,{ctor: "_Tuple2",_0: "3",_1: 3} + ,{ctor: "_Tuple2",_0: "4",_1: 4} + ,{ctor: "_Tuple2" + ,_0: "5" + ,_1: 5}])); + var fmtMailbox = $Signal.mailbox("base64"); + var fmtDropdown = A2($Graphics$Input.dropDown, + $Signal.message(fmtMailbox.address), + _L.fromArray([{ctor: "_Tuple2" + ,_0: "base64" + ,_1: "base64"} + ,{ctor: "_Tuple2" + ,_0: "hex" + ,_1: "hex"}])); + var lenMailbox = $Signal.mailbox(10); + var lenDropdown = A2($Graphics$Input.dropDown, + $Signal.message(lenMailbox.address), + _L.fromArray([{ctor: "_Tuple2" + ,_0: "10" + ,_1: 10} + ,{ctor: "_Tuple2" + ,_0: "27" + ,_1: 27} + ,{ctor: "_Tuple2" + ,_0: "40" + ,_1: 40}])); + var makePass = function (m) { + return function () { + var sha1 = $Sha.createHash("sha1"); + var s_concatenated = _U.cmp(m.nb, + 0) > 0 ? A2($Basics._op["++"], + m.master.string, + $Basics.toString(m.nb)) : m.master.string; + var concatenated = A2($Basics._op["++"], + s_concatenated, + m.domain.string); + var hash = A3($Sha.update, + concatenated, + "utf8", + sha1); + return A3($String.slice, + 0, + m.len, + A2($Sha.digest,m.fmt,hash)); + }(); + }; + var update = F2(function (action, + m) { + return function () { + var tmp = function () { + switch (action.ctor) + {case "DomainChanged": + return _U.replace([["domain" + ,action._0]], + m); + case "FormatChanged": + return _U.replace([["fmt" + ,action._0]], + m); + case "LengthChanged": + return _U.replace([["len" + ,action._0]], + m); + case "MasterChanged": + return _U.replace([["master" + ,action._0]], + m); + case "NbChanged": + return _U.replace([["nb" + ,action._0]], + m);} + _U.badCase($moduleName, + "between lines 60 and 66"); + }(); + return _U.replace([["pass" + ,makePass(tmp)]], + tmp); + }(); + }); + var basicWidth = 320; + var introduction = $Graphics$Element.width(basicWidth)(A3($Graphics$Element.container, + basicWidth, + 210, + $Graphics$Element.middle)($Graphics$Element.width(220)($Markdown.toElement("\n# YPassword\n\nSimply enter\n- the domain name\n- your master password\n- Max len / format\n- Nb (if you want to change your password)\n")))); + var passwordView = function (pass) { + return $Graphics$Element.color($Color.grey)(A3($Graphics$Element.container, + basicWidth, + 30, + $Graphics$Element.middle)($Graphics$Element.width(basicWidth)($Graphics$Element.centered($Text.monospace($Text.fromString(pass)))))); + }; + var textInput = function (fieldContent) { + return $Graphics$Element.width(basicWidth)(A4($Graphics$Input$Field.field, + yStyle(18), + $Signal.message(domainContent.address), + "Domain Name", + fieldContent)); + }; + var passInput = function (fieldContent) { + return $Graphics$Element.width(basicWidth)(A4($Graphics$Input$Field.password, + yStyle(18), + $Signal.message(masterContent.address), + "Master Password", + fieldContent)); + }; + var view = F2(function (m,_v6) { + return function () { + switch (_v6.ctor) + {case "WindowSizeChanged": + switch (_v6._0.ctor) + {case "_Tuple2": + return A2($Graphics$Element.color, + $Color.lightGrey, + A4($Graphics$Element.container, + A2($Basics.max, + _v6._0._0, + basicWidth), + A2($Basics.max,_v6._0._1,430), + $Graphics$Element.middle, + A2($Graphics$Element.flow, + $Graphics$Element.down, + _L.fromArray([introduction + ,A2($Graphics$Element.spacer, + 10, + 10) + ,textInput(m.domain) + ,A2($Graphics$Element.spacer, + 10, + 10) + ,passInput(m.master) + ,A2($Graphics$Element.spacer, + 10, + 10) + ,A2($Graphics$Element.flow, + $Graphics$Element.right, + _L.fromArray([$Graphics$Element.width(80)(lenDropdown) + ,A2($Graphics$Element.spacer, + (basicWidth - 3 * 80) / 2 | 0, + 10) + ,$Graphics$Element.width(80)(fmtDropdown) + ,A2($Graphics$Element.spacer, + (basicWidth - 3 * 80) / 2 | 0, + 10) + ,$Graphics$Element.width(80)(nbDropdown)])) + ,A2($Graphics$Element.spacer, + 30, + 30) + ,passwordView(m.pass)]))));} + break;} + _U.badCase($moduleName, + "between lines 102 and 121"); + }(); + }); + var initialState = {_: {} + ,domain: $Graphics$Input$Field.noContent + ,fmt: "base64" + ,len: 10 + ,master: $Graphics$Input$Field.noContent + ,nb: 0 + ,pass: ""}; + var WindowSizeChanged = function (a) { + return {ctor: "WindowSizeChanged" + ,_0: a}; + }; + var NbChanged = function (a) { + return {ctor: "NbChanged" + ,_0: a}; + }; + var FormatChanged = function (a) { + return {ctor: "FormatChanged" + ,_0: a}; + }; + var LengthChanged = function (a) { + return {ctor: "LengthChanged" + ,_0: a}; + }; + var DomainChanged = function (a) { + return {ctor: "DomainChanged" + ,_0: a}; + }; + var MasterChanged = function (a) { + return {ctor: "MasterChanged" + ,_0: a}; + }; + var actions = $Signal.mergeMany(_L.fromArray([A2($Signal.map, + MasterChanged, + masterContent.signal) + ,A2($Signal.map, + DomainChanged, + domainContent.signal) + ,A2($Signal.map, + LengthChanged, + lenMailbox.signal) + ,A2($Signal.map, + FormatChanged, + fmtMailbox.signal) + ,A2($Signal.map, + NbChanged, + nbMailbox.signal)])); + var main = A3($Signal.map2, + view, + A3($Signal.foldp, + update, + initialState, + actions), + A2($Signal.map, + WindowSizeChanged, + $Window.dimensions)); + var Model = F6(function (a, + b, + c, + d, + e, + f) { + return {_: {} + ,domain: b + ,fmt: d + ,len: c + ,master: a + ,nb: e + ,pass: f}; + }); + _elm.YPassword.values = {_op: _op + ,Model: Model + ,MasterChanged: MasterChanged + ,DomainChanged: DomainChanged + ,LengthChanged: LengthChanged + ,FormatChanged: FormatChanged + ,NbChanged: NbChanged + ,WindowSizeChanged: WindowSizeChanged + ,actions: actions + ,initialState: initialState + ,basicWidth: basicWidth + ,main: main + ,update: update + ,makePass: makePass + ,lenMailbox: lenMailbox + ,fmtMailbox: fmtMailbox + ,nbMailbox: nbMailbox + ,introduction: introduction + ,view: view + ,lenDropdown: lenDropdown + ,fmtDropdown: fmtDropdown + ,nbDropdown: nbDropdown + ,passwordView: passwordView + ,domainContent: domainContent + ,masterContent: masterContent + ,yStyle: yStyle + ,textInput: textInput + ,passInput: passInput}; + return _elm.YPassword.values; +}; diff --git a/src/YPassword/index.html b/src/YPassword/index.html new file mode 100644 index 0000000..233d0c9 --- /dev/null +++ b/src/YPassword/index.html @@ -0,0 +1,20 @@ + + + +YPassword + + + + + + + diff --git a/src/pubkey.txt b/src/pubkey.txt new file mode 100644 index 0000000..95ee264 --- /dev/null +++ b/src/pubkey.txt @@ -0,0 +1,95 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: GPGTools - https://gpgtools.org + +mQINBFcCwLsBEACbChR/C0nKL+aNvZss50IupbkomYYCMw/hH8GcxOw6RgMiMwac +qnbUc46Qpv0n6yIL/mJrSroGslWgf9jAEhAd5YC8wiRnpFdo/0nQzF+6Hp4D6pBd +kbO2nbSYStJOSsQahmclL6ZOt1v57mcgFTDjgACegg6qRnmmHaGt3nr7v58J3Ziz +gFGlpFVb6Rs1F/rtyy6R5UeFWipxM77gxPV18kQvmcugdt2HxEssps84GtZ/ZVEN +Xxy7bYxRwmB52CLpJjK2USdGwa5nhB/FGcuBhVuyfwdQzp8g1uxvSovPhtU0Ghr1 +1q2z50BqHvr+9a5k2NJaSLB0o/ZvJlHL6G+JYiwh/z0/bBtkNUcWw8f7JFWbpW/5 +wu8UVY9aeA2Cw4ZiSFyeYp8er0wCP4Ck48V+5RkeCN+MSPNdroQ2v6FkcVTrnd0y +As7/FQccEIv8uFmRV3tywS450jKHXSAPVEA0qHgE93F2CCIXRC/AQv7BYh1YPRIi +x0/Wxqp29THI9u5Pe5BpZGRPUsJfxfg7cc/RSM5bUS2n2xbhKMnHl3PuybiWAYJE +4XUCyVz5vtWS0B5V/23hJJ1LaMpxgMDc9GrbUL2okTR5p0AM/r4R4IwDQQ8+SIn9 +LJSKgX6MlSBo8umTZQHpYCPCIUuBlnVALcehKsDp9P0He38+nYGIP6iiqQARAQAB +tCdZYW5uIEVzcG9zaXRvIDx5YW5uLmVzcG9zaXRvQGdtYWlsLmNvbT6JAkAEEwEK +ACoCGwMFCQeGH4AFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AFAlcCwVICGQEACgkQ +exmkxlDVlkaang/+NparQKCCh7D/NUd2PJZZPWGI/hiyQOIF1vGzxfGWRUHEJGkh +hhPcdlhyfKaBHu52S8QvH0R1PGRll/zYM1BvPK2xj6bSwGQdY0ayGHi883ODQf9V +Yc9au6gSG9OwOjfqBp4kp6ocQYdhDI8e+GabtZ75rTvj/nEW1x8GWizkILQy1jb7 +F2eSLOhMFAF1YTVPm2vinp6C2NSFtszJsL2VBoPgtzjQF+BaUUk7VA9zChRUp2mi +IE7D51vXvjkGfrZhEtWIrZTpPUQqIIR9GLGPwKAK1rG5vWLvjOc1wO9iREPa+0K9 +LYCD2S08NTghI6a7RGqNub2NwhpghPP6cVJIAzGsROQKokolDxTuADIXw9fzhodh +tpJao7hcjT7h7IpX4bpS6GmK+dk2l23P80i5cP3AMKZSEfZ24V0Ji8YSHb9ebdvp +PMeyTIWPa1Vp5HM1iufRl9VxW+NENbga28MOvssE3KRl09n6AjDpt3aR5wNdzW/4 +poOGW6211FPzeHlvJnr8VT9g1z5rATLtjrdsHsN/BjQjJXrSb58qKz6f2NSOe/gQ +AUQRIXthkj/V62NhFD9g1gdsftqQsHrHNPFawaOfA/ShB6tNY532FlFudZDcXlFP +yvwGjT1iRY7do0HCbkgvI1kA8ztEzmcThG0gKkMPtZrPM7xSqVB6KNF8WgWJAj0E +EwEKACcFAlcCwLsCGwMFCQeGH4AFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQ +exmkxlDVlkYq5A/+OeVHb2nQ+xsqyKUaBq+l306EBlTAZ0O+8a1lSwdCl8SVJ4Qh +vOITcXtA7NQB4WfYCIbPca4IPub/0shHBHKjEVPNLq3G0esUB59sZNlsxFRs/Nse +Fdes7PRvI0SIcT3ykNs58cxVNvTWxQT+tVo3XUPjf8gjqNjYl2QFVj3Rafm8vBRo +LGIJDAvnNmWNxKr+xUwHrP6Hw1dDnKCJIpYikLbcpVPdXL5DG1mQu6otY+Uqaf5k +FrN7YYM3yvlGIeUClWgnd0nr9N4/gQuRcGuh0BXRHp/EUEUkGU7Smg2Rv5n/63iv +vhGPuB497EEw39/dk/lq3pLI3qUnI2tZa8NWe4amPU3WwWn/eGx7i7A95k0TFHxV +UoV5KzPj75OTUUD1FSx1Q8dyWEGAUfLse44/iKHjdVZVtSNTzfT3I5+aiT9Py5hr +vKZaPDnsDgKDxeviIN2WdhDxEfbUOhfFAoxowba+1QKiDdbDSOTycF1pxxUOGk7X +5n+EKGOjrmXU8DXpQByjZWeO8cLcfgntx3XjgJINCnAlmVQdfU4mLig8Kel9jDbj +AP0ZFSneiJ9zZbYADPr6WRjj+u61ufTQc64PZu22Zv8zBVnAQcMnWpecRKEz4Lnk +CjZ2EOEZ4g4EvMzEA1oNm5DeMBONbZeq9G49jaHZmesNyxVgQqZLSJBzxP65Ag0E +VwLAuwEQAK5yw9rGckpA70Y2UEoQNheZm1DLIO4IQ+m8FO417K/9cO0kLpvtMeCH +O9MnPgpDBr43PHLzZgDY7hzHEdZYMwk8DQLjL/0QfH6Dg9Sqh/DrLfVehOOBbo8Q +Fx9K3/GwNgKSatj32MKk9w/jlizkJPm6ovWY/OERM/CTYQWYpPi3cv22zqyKQK1V +K/n91+1Q+mEwr1rUhCahXuo+JCQ8XGLtUhg9xFjJQbiBESEd2k8JafTBezFN1dYv +ioQy87RXmzxZANN3KB91JfiRUm0U/Lw/4y4a+GQkb3/GCMnkZgrdQxW5Yj7tB4SA +4bIiXJ5XVG/pRjm8OQbsF9GDLWG0keWVEeZj8OFBSpOOPEgEG9s46RO2VpVNKfEg +DHV/8s4Wcm3Ocno/0XSxrzGWJDtUZpJTpnkFdVldolh7+8918t8jgckzlW0XaBmC +ygAeeFhLX+XUiccA9WYUoqAZOgd+rmeUuG6OF65kxYOBv1W0hk3TBQKl3WDLtc/D +3ZSmTE/YzDNgFA9lFGcngjhPEV4JldZO3oz9dsmPzzITAilQ2IH800yEUCdACIpo +bOapaeAHCBNNwPnoDpulJtB6CGz7GfkAm172lT+5QQ5T5SnZTLEc8CATK6YP9Kpx +QBrQiqtL6Cqk4TBYGVhvlBL3SRtZ7vXjyAGKChr4hYexfeguyEjvABEBAAGJAiUE +GAEKAA8FAlcCwLsCGwwFCQeGH4AACgkQexmkxlDVlkakMA/9EvmgS+9b+KGvm2q7 +BZgI3ts9lnqtg/luI7QZVIXi+ViThGNCdvhLZovwnsNYlQSTP9C9iHr2bITJ3/AT +X/rqbhtoXjxtcc4zS2Ruy8jJjJ1vtalg6PdK3R5VqyWeNssXYSMf1PBiJ03JlASl +co9+p4PGeZSEiIBEwx5wfuixGuDK8H5uU7JpxqSBhW26mOXpy1zPHqzBdMv27CZ3 +KR/vQfw1Cz8tH+iolcwB1u5qqUGBh+lVLhTbIpFyH42eaDr88kNQCkAAlAZ9aWwA +iz9P9djqBFdYl7UfotFYVqc4RDGGzfLRIth2MzGFd/+/pJJ0+XyyvxobAVgKXKkN +8WOvZggK3a7Kz165ue3Rp5doYcixNv7zqByt+12NsEpksFOtJmCCOD9y8LCATvlI +6cK2crthQ04H+QpWP2kW2KNoi5qjQhBuT+zyoC+Zl01gZsYlGYUA492Ca2Se4TrX +l0si5sIGzkcPSBDLmokhPWZJWfxVNSygpv/8xBZCDjB6n54AuoF7EWW33b/cgkgI +D4XOyD8cLhalnDmYl9ihGgoN32njD2Xy5PL9JaFbPnw4Qt0H93DX0xvJt5LKwiEw +94DsCGcRl/b/Ea0zmeE/w9N1Tu5Awpx0XsLhYhjBf9I41SZdDtGcpzQlkv1AFkHA +BMmOiLqNQIhozetjRN29Ir4VXGeZAQ0EUe/OGAEIALdQNOPlZmBUMGaQNQmARcf3 +pCXa6Hhf/xrSJPkZhqlURzd16UWJes9ioX9t+bLAc7KDxKoLxRYfPQ8gW/QWNG2i +nUUnYXXoKOiqIi8FjWGyiNghjfS/2LYD+C3AouENaZJo1BcZN+dBLSVR+8hNv8VQ +8uXeNqIP0afWnhq9V08mUjdpbgTpCGHiIkqVITckhDpqKojFPWyxpUr+xuckmD5Q +EMUriKutDs+n7LSJWyb4CGzMEl/2wIOkk8bO7Re+YEsVnlySRRLr7ma5lOHfpZbe +tUYyckUSDzzehFxM42y80eGqaGVH79E/4daC4+zH+bJFhED/aglq4zhMNti8IKMA +EQEAAbQnWWFubiBFc3Bvc2l0byA8eWFubi5lc3Bvc2l0b0BnbWFpbC5jb20+iQE+ +BBMBAgAoBQJR784YAhsvBQkHhh+ABgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAK +CRA9jpx8n6DzrIAfB/0QIzNDGf3YGy6q3O+ov2TbrTbciGPg88GcvDsYiHGbX2Kh +5EkhFp/jvazy7o+8sVETm3niWhFJXEgwa7y8VxRfPAeioRlD2jD/PQs0/G5Kp3EY +GM+0WZSF4bHBGYr4YubKSFQb4bu3tjMlTvorGpw6T+1u5LaKBTVAH67/GzFY4e9X +nzEY98oaw8prrd5+R8dteKCQJEvXkM56a6TqKGugUdKOi7uB0DLag5v/9+v53jUl +vRjEA4dlgB6JUV+W8Yg+dKkwzgofMbrQV7YZi/LTiRViah8AWTtMXJ9YXLc6vZlu +Dx3BXD7z8dl3GzOruN53Y2Z/zozepkjjk+edTFqsuQENBFHvzhgBCAC03YnnL43j +oEiR9XF6+Ofuqre83GTtZMefbi9qWg0MlDLKYiO79k0NUXbC8AEOqF6iKl1ofTzN +VB7Tjxs0LCuAJ7Bv+me7tkb3FuHHcj9byUNzO1qs0FU+Z1T1tpwPFIPasj+wFaQB +JAYn/a/mc1sVoT9E/s+NVZIHnDx4qBrwUEOMmYuZ4Zy6Z3Cj74CV6fRBCvBZRo+f +QHdgNnuAcQVuts1HTsDQ8Cavq15hPaVw4C3z+Oqq38yJkWWdZLtc8pWuHyauO1nF +rHm+Rut2n/tLU/mCllutGO0AKHH1NVARDf8ow5I3cwYqxfU6AfYMk8ouyCebjK1R +zUHG8tsqTUDdABEBAAGJAkQEGAECAA8FAlHvzhgCGy4FCQeGH4ABKQkQPY6cfJ+g +86zAXSAEGQECAAYFAlHvzhgACgkQme8vc/AYUS9M8QgArUeI039UG7yfQIr+k1mW +T7xugOj12wMpcz0JT9KhbSeKnJ/jzFreBW4rel/vN2jQrAznFKCJX2SMo2WheAns +4exOAgaNlw6OCz8AJXhYMlblDqU9P60Db2bOqjoCIVdTPzWqmOEMH82Fzdz2TblR +0Bqd53rvZURAIE4nIUJJF7K7reI+KUhwB3O4kGEeiOH+UhNTqndBSIom4THxu+ZX +RegwMrHLgNI7cWYHJb1hx/UvSXIfV2CGk1B4jY0mdo1yy9SK8RDARfVHFBN6L3i+ +6O0Oki7VAcczMib6X8JZrQzHAdv+0dzulcCSXWDEkDo+679U4jBMDXlukcSRLZoE +Qsy/B/93n5sHUazBuLNPSMiRyk2jSRrg67XBsdid0eyVW2hdNJkKrl0i5H+SkHhA +rJvIWnko+Uf3i1TZsaqy96rkvgRWc/zkhgjHFQHzh+kvWgZqPZPRJb+xov05aIWI +RohrRWa9OTylobQg/O3LbYtyP3QddLndZeBcVMe+el49/nbG/czr5UeFzL25rrn4 +JP+3GluEK8i67DKB5r2zr0py2DW/4JjcYSPEWDXUy2n/RaSZJRY52b+mZl3Lx0jY +OBGHGpEdVRA/xdTg2tyS893xM++D7UJM+1NSsx1k3jCJ0+91psrHc7xW4bwpZmE7 +V3TsAeh+rYxJHCKnbZ3SbOEyEqtr +=Rjt+ +-----END PGP PUBLIC KEY BLOCK-----