diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d359ca6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://EditorConfig.org + +root = true + +; Use 2 spaces for indentation in all Ruby files + +[*.rb] +indent_style = space +indent_size = 2 + +[Rakefile] +indent_style = space +indent_size = 2 + +[Gemfile*] +indent_style = space +indent_size = 2 + +[config.ru] +indent_style = space +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68a6830 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.bundle +.DS_Store +.sass-cache +.gist-cache +.pygments-cache +_deploy +public +sass.old +source.old +source/_stash +source/stylesheets/screen.css +vendor +node_modules diff --git a/.powrc b/.powrc new file mode 100644 index 0000000..4777f08 --- /dev/null +++ b/.powrc @@ -0,0 +1,5 @@ +if [ -f "$rvm_path/scripts/rvm" ] && [ -f ".rvmrc" ] ; then + source "$rvm_path/scripts/rvm" + source ".rvmrc" +fi + \ No newline at end of file diff --git a/.slugignore b/.slugignore new file mode 100644 index 0000000..0a41d01 --- /dev/null +++ b/.slugignore @@ -0,0 +1,3 @@ +plugins +sass +source diff --git a/.themes/classic/.editorconfig b/.themes/classic/.editorconfig new file mode 100644 index 0000000..940fea3 --- /dev/null +++ b/.themes/classic/.editorconfig @@ -0,0 +1,44 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://EditorConfig.org + + +; Use 2 spaces for indentation in SCSS, JavaScript, HTML, and XML + +[*.scss] +indent_style = space +indent_size = 2 + +[*.js] +indent_style = space +indent_size = 2 + +[*.html] +indent_style = space +indent_size = 2 + +[*.xml] +indent_style = space +indent_size = 2 + + +; Use 4 spaces for indentation in Markdown files + +[*.md] +indent_style = space +indent_size = 4 + +[*.markdown] +indent_style = space +indent_size = 4 + + +; Override default indentation for some library files + +[jwplayer/glow/glow.xml] +indent_style = tab + +[libs/jXHR.js] +indent_style = tab + +[libs/swfobject-dynamic.js] +indent_style = tab diff --git a/.themes/classic/sass/_base.scss b/.themes/classic/sass/_base.scss new file mode 100644 index 0000000..05fdd00 --- /dev/null +++ b/.themes/classic/sass/_base.scss @@ -0,0 +1,5 @@ +@import "base/utilities"; +@import "base/solarized"; +@import "base/theme"; +@import "base/typography"; +@import "base/layout"; diff --git a/.themes/classic/sass/_partials.scss b/.themes/classic/sass/_partials.scss new file mode 100644 index 0000000..99c28b6 --- /dev/null +++ b/.themes/classic/sass/_partials.scss @@ -0,0 +1,8 @@ +@import "partials/header"; +@import "partials/navigation"; +@import "partials/blog"; +@import "partials/sharing"; +@import "partials/syntax"; +@import "partials/archive"; +@import "partials/sidebar"; +@import "partials/footer"; diff --git a/.themes/classic/sass/base/_layout.scss b/.themes/classic/sass/base/_layout.scss new file mode 100644 index 0000000..8190342 --- /dev/null +++ b/.themes/classic/sass/base/_layout.scss @@ -0,0 +1,192 @@ +$max-width: 1200px !default; + +// Padding used for layout margins +$pad-min: 18px !default; +$pad-narrow: 25px !default; +$pad-medium: 35px !default; +$pad-wide: 55px !default; + +// Sidebar widths used in media queries +$sidebar-width-medium: 240px !default; +$sidebar-pad-medium: 15px !default; +$sidebar-pad-wide: 20px !default; +$sidebar-width-wide: 300px !default; + +$indented-lists: false !default; + +$header-font-size: 1em !default; +$header-padding-top: 1.5em !default; +$header-padding-bottom: 1.5em !default; + +.group { @include pie-clearfix; } + +@mixin collapse-sidebar { + float: none; + width: auto; + clear: left; + margin: 0; + padding: 0 $pad-medium 1px; + background-color: lighten($sidebar-bg, 2); + border-top: 1px solid lighten($sidebar-border, 4); + section { + &.odd, &.even { float: left; width: 48%; } + &.odd { margin-left: 0; } + &.even { margin-left: 4%; } + } + &.thirds section { + width: 30%; + margin-left: 5%; + &.first { + margin-left: 0; + clear: both; + } + } +} + +body { + -webkit-text-size-adjust: none; + max-width: $max-width; + position: relative; + margin: 0 auto; + > header, > nav, > footer, #content > article, #content > div > article, #content > div > section { + @extend .group; + padding-left: $pad-min; + padding-right: $pad-min; + @media only screen and (min-width: 480px) { + padding-left: $pad-narrow; + padding-right: $pad-narrow; + } + @media only screen and (min-width: 768px) { + padding-left: $pad-medium; + padding-right: $pad-medium; + } + @media only screen and (min-width: 992px) { + padding-left: $pad-wide; + padding-right: $pad-wide; + } + } + div.pagination { + @extend .group; + margin-left: $pad-min; + margin-right: $pad-min; + @media only screen and (min-width: 480px) { + margin-left: $pad-narrow; + margin-right: $pad-narrow; + } + @media only screen and (min-width: 768px) { + margin-left: $pad-medium; + margin-right: $pad-medium; + } + @media only screen and (min-width: 992px) { + margin-left: $pad-wide; + margin-right: $pad-wide; + } + } + > header { + font-size: $header-font-size; + padding-top: $header-padding-top; + padding-bottom: $header-padding-bottom; + } +} + +#content { + overflow: hidden; + > div, > article { width: 100%; } +} + +aside.sidebar { + float: none; + padding: 0 $pad-min 1px; + background-color: lighten($sidebar-bg, 2); + border-top: 1px solid $sidebar-border; + @extend .group; +} + +.flex-content { max-width: 100%; height: auto; } + +.basic-alignment { + &.left { float: left; margin-right: 1.5em; } + &.right { float: right; margin-left: 1.5em; } + &.center { display:block; margin: 0 auto 1.5em; } + &.left, &.right { margin-bottom: .8em; } +} + +.toggle-sidebar { &, .no-sidebar & { display: none; }} + +body.sidebar-footer { + @media only screen and (min-width: 750px) { + aside.sidebar{ @include collapse-sidebar; } + } + #content { margin-right: 0px; } + .toggle-sidebar { display: none; } +} + +@media only screen and (min-width: 550px) { + body > header { font-size: $header-font-size; } +} +@media only screen and (min-width: 750px) { + aside.sidebar { @include collapse-sidebar; } +} +#main, #content, .sidebar { + @extend .group; +} +@media only screen and (min-width: 768px) { + body { -webkit-text-size-adjust: auto; } + body > header { font-size: $header-font-size * 1.2; } + #main { + padding: 0; + margin: 0 auto; + } + #content { + overflow: visible; + margin-right: $sidebar-width-medium; + position: relative; + .no-sidebar & { margin-right: 0; border-right: 0; } + .collapse-sidebar & { margin-right: 20px; } + > div, > article { + padding-top: $pad-medium/2; + padding-bottom: $pad-medium/2; + float: left; + } + } + aside.sidebar { + width: $sidebar-width-medium - $sidebar-pad-medium*2; + padding: 0 $sidebar-pad-medium $sidebar-pad-medium; + background: none; + clear: none; + float: left; + margin: 0 -100% 0 0; + section { + width: auto; margin-left: 0; + &.odd, &.even { float: none; width: auto; margin-left: 0; } + } + .collapse-sidebar & { + @include collapse-sidebar; + } + } +} + +@media only screen and (min-width: 992px) { + body > header { font-size: $header-font-size * 1.3; } + #content { margin-right: $sidebar-width-wide; } + #content { + > div, > article { + padding-top: $pad-wide/2; + padding-bottom: $pad-wide/2; + } + } + aside.sidebar { + width: $sidebar-width-wide - $sidebar-pad-wide*2; + padding: 1.2em $sidebar-pad-wide $sidebar-pad-wide; + .collapse-sidebar & { + padding: { left: $pad-wide; right: $pad-wide; } + } + } +} + +@if $indented-lists == false { + @media only screen and (min-width: 768px) { + ul, ol { margin-left: 0; } + } +} + diff --git a/.themes/classic/sass/base/_solarized.scss b/.themes/classic/sass/base/_solarized.scss new file mode 100644 index 0000000..45d8fc5 --- /dev/null +++ b/.themes/classic/sass/base/_solarized.scss @@ -0,0 +1,46 @@ +$base03: #002b36 !default; //darkest blue +$base02: #073642 !default; //dark blue +$base01: #586e75 !default; //darkest gray +$base00: #657b83 !default; //dark gray +$base0: #839496 !default; //medium gray +$base1: #93a1a1 !default; //medium light gray +$base2: #eee8d5 !default; //cream +$base3: #fdf6e3 !default; //white +$solar-yellow: #b58900 !default; +$solar-orange: #cb4b16 !default; +$solar-red: #dc322f !default; +$solar-magenta: #d33682 !default; +$solar-violet: #6c71c4 !default; +$solar-blue: #268bd2 !default; +$solar-cyan: #2aa198 !default; +$solar-green: #859900 !default; + +$solarized: dark !default; + +@if $solarized == light { + + $_base03: $base03; + $_base02: $base02; + $_base01: $base01; + $_base00: $base00; + $_base0: $base0; + $_base1: $base1; + $_base2: $base2; + $_base3: $base3; + + $base03: $_base3; + $base02: $_base2; + $base01: $_base1; + $base00: $_base0; + $base0: $_base00; + $base1: $_base01; + $base2: $_base02; + $base3: $_base03; +} + +/* non highlighted code colors */ +$pre-bg: $base03 !default; +$pre-border: darken($base02, 5) !default; +$pre-color: $base1 !default; + + diff --git a/.themes/classic/sass/base/_theme.scss b/.themes/classic/sass/base/_theme.scss new file mode 100644 index 0000000..c35136d --- /dev/null +++ b/.themes/classic/sass/base/_theme.scss @@ -0,0 +1,85 @@ +$noise-bg: image-url('noise.png') top left !default; +$img-border: inline-image('dotted-border.png') !default; + +// Main Link Colors +$link-color: lighten(#165b94, 3) !default; +$link-color-hover: adjust-color($link-color, $lightness: 10, $saturation: 25) !default; +$link-color-visited: adjust-color($link-color, $hue: 80, $lightness: -4) !default; +$link-color-active: adjust-color($link-color-hover, $lightness: -15) !default; + +// Main Section Colors +$main-bg: #f8f8f8 !default; +$page-bg: #252525 !default; +$article-border: #eeeeee !default; + +$header-bg: #333 !default; +$header-border: lighten($header-bg, 15) !default; +$title-color: #f2f2f2 !default; +$subtitle-color: #aaa !default; + +$text-color: #222 !default; +$text-color-light: #aaa !default; +$type-border: #ddd !default; + +/* Navigation */ +$nav-bg: #ccc !default; +$nav-bg-front: image-url('noise.png') !default; +$nav-bg-back: linear-gradient(lighten($nav-bg, 8), $nav-bg, darken($nav-bg, 11)) !default; +$nav-color: darken($nav-bg, 38) !default; +$nav-color-hover: darken($nav-color, 25) !default; +$nav-placeholder: desaturate(darken($nav-bg, 10), 15) !default; +$nav-border: darken($nav-bg, 10) !default; +$nav-border-top: lighten($nav-bg, 15) !default; +$nav-border-bottom: darken($nav-bg, 25) !default; +$nav-border-left: darken($nav-bg, 11) !default; +$nav-border-right: lighten($nav-bg, 7) !default; + +/* Sidebar colors */ +$sidebar-bg: #f2f2f2 !default; +$sidebar-link-color: $text-color !default; +$sidebar-link-color-hover: $link-color-hover !default; +$sidebar-link-color-active: $link-color-active !default; +$sidebar-color: change-color(mix($text-color, $sidebar-bg, 80), $hue: hue($sidebar-bg), $saturation: saturation($sidebar-bg)/2) !default; +$sidebar-border: desaturate(darken($sidebar-bg, 7), 10) !default; +$sidebar-border-hover: darken($sidebar-bg, 7) !default; +$sidebar-link-color-subdued: lighten($sidebar-color, 20) !default; +$sidebar-link-color-subdued-hover: $sidebar-link-color-hover !default; + +$footer-color: #888 !default; +$footer-bg: #ccc !default; +$footer-bg-front: image-url('noise.png') !default; +$footer-bg-back: linear-gradient(lighten($footer-bg, 8), $footer-bg, darken($footer-bg, 11)) !default; +$footer-color: darken($footer-bg, 38) !default; +$footer-color-hover: darken($footer-color, 10) !default; +$footer-border-top: lighten($footer-bg, 15) !default; +$footer-border-bottom: darken($footer-bg, 15) !default; +$footer-link-color: darken($footer-bg, 38) !default; +$footer-link-color-hover: darken($footer-color, 25) !default; +$page-border-bottom: darken($footer-bg, 5) !default; + + +/* Core theme application */ + +a { + @include link-colors($link-color, $hover: $link-color-hover, $focus: $link-color-hover, $visited: $link-color-visited, $active: $link-color-active); +} +aside.sidebar a { + @include link-colors($sidebar-link-color, $hover: $sidebar-link-color-hover, $focus: $sidebar-link-color-hover, $active: $sidebar-link-color-active); +} +a { + @include transition(color .3s); +} + +html { + background: $page-bg image-url('line-tile.png') top left; +} +body { + > div { + background: $sidebar-bg $noise-bg; + border-bottom: 1px solid $page-border-bottom; + > div { + background: $main-bg $noise-bg; + border-right: 1px solid $sidebar-border; + } + } +} diff --git a/.themes/classic/sass/base/_typography.scss b/.themes/classic/sass/base/_typography.scss new file mode 100644 index 0000000..9a6bbac --- /dev/null +++ b/.themes/classic/sass/base/_typography.scss @@ -0,0 +1,164 @@ +$blockquote: $type-border !default; +$sans: "PT Sans", "Helvetica Neue", Arial, sans-serif !default; +$serif: "PT Serif", Georgia, Times, "Times New Roman", serif !default; +$mono: Menlo, Monaco, "Andale Mono", "lucida console", "Courier New", monospace !default; +$heading-font-family: "PT Serif", "Georgia", "Helvetica Neue", Arial, sans-serif !default; +$header-title-font-family: $heading-font-family !default; +$header-subtitle-font-family: $heading-font-family !default; + +// Fonts +.heading { + font-family: $heading-font-family; +} +.sans { font-family: $sans; } +.serif { font-family: $serif; } +.mono { font-family: $mono; } + +body > header h1 { + font-size: 2.2em; + @extend .heading; + font-family: $header-title-font-family; + font-weight: normal; + line-height: 1.2em; + margin-bottom: 0.6667em; +} +body > header h2 { + font-family: $header-subtitle-font-family; +} + +body { + line-height: 1.5em; + color: $text-color; + @extend .serif; +} +h1 { + font-size: 2.2em; + line-height: 1.2em; +} + +@media only screen and (min-width: 992px) { + body { font-size: 1.15em; } + h1 { font-size: 2.6em; line-height: 1.2em; } +} + +#{headings()}{ + @extend .heading; + text-rendering: optimizelegibility; + margin-bottom: 1em; + font-weight: bold; +} +h2, section h1 { + font-size: 1.5em; +} +h3, section h2, section section h1 { + font-size: 1.3em; +} +h4, section h3, section section h2, section section section h1 { + font-size: 1em; +} +h5, section h4, section section h3 { + font-size: .9em; +} +h6, section h5, section section h4, section section section h3 { + font-size: .8em; +} + +p, article blockquote, ul, ol { margin-bottom: 1.5em; } + +ul { list-style-type: disc; + ul { list-style-type: circle; margin-bottom: 0px; + ul { list-style-type: square; margin-bottom: 0px; }}} + +ol { list-style-type: decimal; + ol { list-style-type: lower-alpha; margin-bottom: 0px; + ol { list-style-type: lower-roman; margin-bottom: 0px; }}} + +ul, ol { &, ul, ol { margin-left: 1.3em; }} +ul, ol { ul, ol { margin-bottom: 0em; }} + +strong { font-weight: bold; } + +em { font-style: italic; } + +sup, sub { font-size: 0.75em; position: relative; display: inline-block; padding: 0 .2em; line-height: .8em;} +sup { top: -.5em; } +sub { bottom: -.5em; } + +a[rev='footnote']{ font-size: .75em; padding: 0 .3em; line-height: 1; } + +q { font-style: italic; + &:before { content: "\201C"; } + &:after { content: "\201D"; } +} + +em, dfn { font-style: italic; } + +strong, dfn { font-weight: bold; } + +del, s { text-decoration: line-through; } + +abbr, acronym { border-bottom: 1px dotted; cursor: help; } + +pre, code, tt { @extend .mono; } + +hr { margin-bottom: 0.2em; } + +small { font-size: .8em; } + +big { font-size: 1.2em; } + +article blockquote { + $bq-margin: 1.2em; + font-style: italic; + position: relative; + font-size: 1.2em; + line-height: 1.5em; + padding-left: 1em; + border-left: 4px solid rgba($text-color-light, .5); + cite { + font-style: italic; + a { color: $text-color-light !important; word-wrap: break-word; } + &:before { content: '\2014'; padding:{right: .3em; left: .3em;} color: $text-color-light; } + } + @media only screen and (min-width: 992px) { + padding-left: 1.5em; + border-left-width: 4px; + } +} + +.pullquote-right:before, +.pullquote-left:before { + /* Reset metrics. */ + padding: 0; + border: none; + + /* Content */ + content: attr(data-pullquote); + + /* Pull out to the right, modular scale based margins. */ + float: right; + width: 45%; + margin: .5em 0 1em 1.5em; + + /* Baseline correction */ + position: relative; + top: 7px; + font-size: 1.4em; + line-height: 1.45em; +} + +.pullquote-left:before { + /* Make left pullquotes align properly. */ + float: left; + margin: .5em 1.5em 1em 0; +} + +/* @extend this to force long lines of continuous text to wrap */ +.force-wrap { + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + white-space: pre-wrap; + word-wrap: break-word; +} + diff --git a/.themes/classic/sass/base/_utilities.scss b/.themes/classic/sass/base/_utilities.scss new file mode 100644 index 0000000..2d49e65 --- /dev/null +++ b/.themes/classic/sass/base/_utilities.scss @@ -0,0 +1,28 @@ +@mixin mask-image($img, $repeat: no-repeat){ + @include experimental(mask-image, image-url($img), -webkit, -moz, -o, -ms); + @include experimental(mask-repeat, $repeat, -webkit, -moz, -o, -ms); + width: image-width($img); + height: image-height($img); +} + +@mixin shadow-box($border: #fff .5em solid, $shadow: rgba(#000, .15) 0 1px 4px, $border-radius: .3em) { + @include border-radius($border-radius); + @include box-shadow($shadow); + @include box-sizing(border-box); + border: $border; +} + +@mixin selection($bg, $color: inherit, $text-shadow: none){ + * { + &::-moz-selection { background: $bg; color: $color; text-shadow: $text-shadow; } + &::-webkit-selection { background: $bg; color: $color; text-shadow: $text-shadow; } + &::selection { background: $bg; color: $color; text-shadow: $text-shadow; } + } +} + +@function text-color($color, $dark: dark, $light: light){ + $text-color: ( (red($color)*299) + (green($color)*587) + (blue($color)*114) ) / 1000; + $text-color: if($text-color >= 150, $dark, $light); + @return $text-color; +} + diff --git a/.themes/classic/sass/custom/_colors.scss b/.themes/classic/sass/custom/_colors.scss new file mode 100644 index 0000000..740266a --- /dev/null +++ b/.themes/classic/sass/custom/_colors.scss @@ -0,0 +1,43 @@ +// Here you can easily change your sites's color scheme. +// To give it a try, uncomment some of the lines below rebuild your blog, and see how it works. +// If you need a handy color picker try http://hslpicker.com + +//$header-bg: #263347; +//$subtitle-color: lighten($header-bg, 58); +//$nav-bg: desaturate(lighten(#8fc17a, 18), 5); +//$nav-bg-front: image-url('noise.png'); +//$nav-bg-back: linear-gradient(lighten($nav-bg, 8), $nav-bg, darken($nav-bg, 11)); +//$sidebar-bg: desaturate(#eceff5, 8); +//$sidebar-link-color: saturate(#526f9a, 10); +//$sidebar-link-color-hover: darken(#7ab662, 9); +//$footer-bg: #ccc !default; +//$footer-bg-front: image-url('noise.png'); +//$footer-bg-back: linear-gradient(lighten($footer-bg, 8), $footer-bg, darken($footer-bg, 11)); + + +/* To use the light Solarized highlighting theme uncomment the following line */ +//$solarized: light; + +/* If you want to tweak the Solarized colors you can do that here */ +//$base03: #002b36; //darkest blue +//$base02: #073642; //dark blue +//$base01: #586e75; //darkest gray +//$base00: #657b83; //dark gray +//$base0: #839496; //medium gray +//$base1: #93a1a1; //medium light gray +//$base2: #eee8d5; //cream +//$base3: #fdf6e3; //white +//$solar-yellow: #b58900; +//$solar-orange: #cb4b16; +//$solar-red: #dc322f; +//$solar-magenta: #d33682; +//$solar-violet: #6c71c4; +//$solar-blue: #268bd2; +//$solar-cyan: #2aa198; +//$solar-green: #859900; + + +/* Non highlighted code colors */ +//$pre-bg: $base03; +//$pre-border: darken($base02, 5); +//$pre-color: $base1; diff --git a/.themes/classic/sass/custom/_fonts.scss b/.themes/classic/sass/custom/_fonts.scss new file mode 100644 index 0000000..1a6b2a0 --- /dev/null +++ b/.themes/classic/sass/custom/_fonts.scss @@ -0,0 +1,10 @@ +// Here you can easily change font faces which are used in your site. +// To give it a try, uncomment some of the lines below rebuild your blog, and see how it works. your sites's. +// If you love to use Web Fonts, you also need to add some lines to source/_includes/custom/head.html + +//$sans: "Optima", sans-serif; +//$serif: "Baskerville", serif; +//$mono: "Courier", monospace; +//$heading-font-family: "Verdana", sans-serif; +//$header-title-font-family: "Futura", sans-serif; +//$header-subtitle-font-family: "Futura", sans-serif; diff --git a/.themes/classic/sass/custom/_layout.scss b/.themes/classic/sass/custom/_layout.scss new file mode 100644 index 0000000..74c7de9 --- /dev/null +++ b/.themes/classic/sass/custom/_layout.scss @@ -0,0 +1,21 @@ +// Here you can easily change your sites's layout. +// To give it a try, uncomment some of the lines below, make changes, rebuild your blog, and see how it works. + +//$header-font-size: 1em; +//$header-padding-top: 1.5em; +//$header-padding-bottom: 1.5em; + +//$max-width: 1350px; +//$indented-lists: true; + +// Padding used for layout margins +//$pad-min: 18px; +//$pad-narrow: 25px; +//$pad-medium: 35px; +//$pad-wide: 55px; + +// Sidebar widths used in media queries +//$sidebar-width-medium: 240px; +//$sidebar-pad-medium: 15px; +//$sidebar-pad-wide: 20px; +//$sidebar-width-wide: 300px; diff --git a/.themes/classic/sass/custom/_styles.scss b/.themes/classic/sass/custom/_styles.scss new file mode 100644 index 0000000..91ffccc --- /dev/null +++ b/.themes/classic/sass/custom/_styles.scss @@ -0,0 +1,2 @@ +// This File is imported last, and will override other styles in the cascade +// Add styles here to make changes without digging in too much diff --git a/.themes/classic/sass/partials/_archive.scss b/.themes/classic/sass/partials/_archive.scss new file mode 100644 index 0000000..9ef1e82 --- /dev/null +++ b/.themes/classic/sass/partials/_archive.scss @@ -0,0 +1,72 @@ +#archive { + #content > div { &, > article { padding-top: 0; } } +} +#blog-archives { + article { + padding: 1em 0 1em; + position: relative; + background: $img-border bottom left repeat-x; + &:last-child { + background: none; + } + footer { padding: 0; margin: 0;} + } + h1 { color: $text-color; margin-bottom: .3em; } + h2 { display: none; } + h1 { + font-size: 1.5em; + a { + @include hover-link; + color: inherit; + &:hover { color: $link-color-hover; } + font-weight: normal; + display: inline-block; + } + } + a.category, time { + @extend .sans; + color: $text-color-light; + } + color: $text-color-light; + .entry-content { display: none; } + time { + font-size: .9em; + line-height: 1.2em; + .month, .day { display: inline-block; } + .month { text-transform: uppercase; } + } + p { margin-bottom: 1em; } + &, .entry-content { a { @include link-colors(inherit, $link-color-hover); }} + a:hover { color: $link-color-hover; } + @media only screen and (min-width: 550px) { + article { margin-left: 5em; } + h2 { + margin-bottom: .3em; + font-weight: normal; + display: inline-block; + position: relative; top: -1px; + float: left; + &:first-child { padding-top: .75em; } + } + time { + position: absolute; + text-align: right; + left: 0em; + top: 1.8em; + } + .year { display: none; } + article { + padding:{left: 4.5em; bottom: .7em;} + } + a.category { + line-height: 1.1em; + } + } +} +#content > .category { + article { + margin-left: 0; + padding-left: 6.8em; + } + .year { display: inline; } +} diff --git a/.themes/classic/sass/partials/_blog.scss b/.themes/classic/sass/partials/_blog.scss new file mode 100644 index 0000000..57fe7a8 --- /dev/null +++ b/.themes/classic/sass/partials/_blog.scss @@ -0,0 +1,141 @@ +article { + padding-top: 1em; + a { @extend .force-wrap; } + header { + position: relative; + padding-top: 2em; + padding-bottom: 1em; + margin-bottom: 1em; + background: $img-border bottom left repeat-x; + h1 { + margin: 0; + a { text-decoration: none; + &:hover { text-decoration: underline; } } + } + p { + font-size: .9em; + color: $text-color-light; + margin: 0; + &.meta { + @extend .sans; + text-transform: uppercase; + position: absolute; top: 0; + } + } + @media only screen and (min-width: 768px) { + margin-bottom: 1.5em; + padding-bottom: 1em; + background: $img-border bottom left repeat-x; + } + } + h2 { + padding-top: 0.8em; + background: $img-border top left repeat-x; + } + .entry-content & h2:first-child, header + h2 { padding-top: 0; } + h2:first-child, header + h2 { background: none; } + .feature { + padding-top: .5em; + margin-bottom: 1em; + padding-bottom: 1em; + background: $img-border bottom left repeat-x; + font-size: 2.0em; font-style: italic; + line-height: 1.3em; + } + img, video, .flash-video { + @extend .flex-content; + @extend .basic-alignment; + @include shadow-box; + } + video, .flash-video { margin: 0 auto 1.5em; } + video { display: block; width: 100%; } + .flash-video { + > div { + position: relative; + display: block; + padding-bottom: 56.25%; + padding-top: 1px; + height: 0; + overflow: hidden; + iframe, object, embed { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + } + } + > footer { + padding-bottom: 2.5em; + margin-top: 2em; + @extend .sans; + p.meta { + margin-bottom: .8em; + font-size: .85em; + clear: both; + overflow: hidden; + } + .byline + time:before, time +time:before, .comments:before, .byline ~ .categories:before { + @extend .separator; + } + } + +} +article + article { + .blog-index & { + background: $img-border top left repeat-x; + } +} +#content .blog-index { + padding: { top: 0; bottom: 0; } + article { padding-top: 2em; } + article header { background: none; padding-bottom: 0; } + article h1 { + font-size: 2.2em; + a { color: inherit; &:hover { color: $link-color-hover; } } + } + a[rel=full-article] { + background: darken($main-bg, 5); + display: inline-block; + padding: .4em .8em; + margin-right: .5em; + text-decoration: none; + color: mix($text-color, $text-color-light); + @extend .serif; + @include transition(background-color .5s); + &:hover { + background: $link-color-hover; + text-shadow: none; + color: $main-bg; + } + } + footer { + @extend .sans; + margin-top: 1em; + } +} + +.separator { + content: "\2022 "; + padding: 0 .4em 0 .2em; + display: inline-block; +} + +#content div.pagination { + text-align: center; + font-size: .95em; + position: relative; + background: $img-border top left repeat-x; + padding: {top: 1.5em; bottom: 1.5em;} + a { + text-decoration: none; + color: $text-color-light; + &.prev { position: absolute; left: 0; } + &.next { position: absolute; right: 0; } + &:hover { color: $link-color-hover; } + &[href*=archive] { + &:before, &:after { content: '\2014'; padding: 0 .3em; } + } + } +} diff --git a/.themes/classic/sass/partials/_footer.scss b/.themes/classic/sass/partials/_footer.scss new file mode 100644 index 0000000..3741122 --- /dev/null +++ b/.themes/classic/sass/partials/_footer.scss @@ -0,0 +1,19 @@ +body > footer { + @extend .sans; + font-size: .8em; + color: $footer-color; + text-shadow: lighten($footer-bg, 5) 0 1px; + background-color: $footer-bg; + @include background($footer-bg-front, $footer-bg-back); + border-top: 1px solid $footer-border-top; + position: relative; + padding-top: 1em; + padding-bottom: 1em; + margin-bottom: 3em; + @include border-bottom-radius(.4em); + z-index: 1; + a { + @include link-colors($footer-link-color, $footer-link-color-hover, $visited: $footer-link-color); + } + p:last-child { margin-bottom: 0; } +} diff --git a/.themes/classic/sass/partials/_header.scss b/.themes/classic/sass/partials/_header.scss new file mode 100644 index 0000000..e3c6c02 --- /dev/null +++ b/.themes/classic/sass/partials/_header.scss @@ -0,0 +1,18 @@ +body > header { + background: $header-bg; + h1 { + display: inline-block; + margin: 0; + a, a:visited, a:hover { + color: $title_color; + text-decoration: none; + } + } + h2 { + margin: .2em 0 0; + @extend .sans; + font-size: 1em; + color: $subtitle-color; + font-weight: normal; + } +} diff --git a/.themes/classic/sass/partials/_navigation.scss b/.themes/classic/sass/partials/_navigation.scss new file mode 100644 index 0000000..30fa011 --- /dev/null +++ b/.themes/classic/sass/partials/_navigation.scss @@ -0,0 +1,137 @@ +body > nav { + position: relative; + background-color: $nav-bg; + @include background($nav-bg-front, $nav-bg-back); + border: { + top: 1px solid $nav-border-top; + bottom: 1px solid $nav-border-bottom; } + padding-top: .35em; + padding-bottom: .35em; + form { + @include background-clip(padding-box); + margin: 0; padding: 0; + .search { + padding: .3em .5em 0; + font-size: .85em; + font-family: $sans; + line-height: 1.1em; + width: 95%; + @include border-radius(.5em); + @include background-clip(padding-box); + @include box-shadow(lighten($nav-bg, 2) 0 1px); + background-color: lighten($nav-bg, 15); + border: 1px solid $nav-border; + color: #888; + &:focus { + color: #444; + border-color: #80b1df; + @include box-shadow(#80b1df 0 0 4px, #80b1df 0 0 3px inset); + background-color: #fff; + outline: none; + } + } + } + fieldset[role=search]{ float: right; width: 48%; } + fieldset.mobile-nav{ float: left; width: 48%; + select{ width: 100%; font-size: .8em; border: 1px solid #888;} + } + ul { display: none; } + @media only screen and (min-width: 550px) { + font-size: .9em; + ul { + @include horizontal-list(0); + float: left; + display: block; + padding-top: .15em; + } + ul.subscription { + margin-left: .8em; + float: right; + li:last-child a { padding-right: 0; } + } + ul li { + margin: 0; + } + a { + @include link-colors($nav-color, $nav-color-hover, $visited: $nav-color); + font-family: $sans; + text-shadow: lighten($nav-bg, 12) 0 1px; + float: left; + text-decoration: none; + font-size: 1.1em; + padding: .1em 0; + line-height: 1.5em; + } + li + li { + border-left: 1px solid $nav-border-left; + margin-left: .8em; + a { + padding-left: .8em; + border-left: 1px solid $nav-border-right; + } + } + form { + float: right; + text-align: left; + padding-left: .8em; + width: $sidebar-width-medium - $pad-medium*2 - $sidebar-pad-medium + 20px; + .search { + width: 93%; + font-size: .95em; + line-height: 1.2em; + } + } + ul[data-subscription$=email] + form { + width: $sidebar-width-medium - $pad-medium*2 - $sidebar-pad-medium - 58px; + .search { width: 91%; } + } + fieldset.mobile-nav { display: none; } + fieldset[role=search]{ width: 99%; } + } + @media only screen and (min-width: 992px) { + form { + width: $sidebar-width-wide - $pad-wide - $sidebar-pad-wide*2 + 10px; + } + ul[data-subscription$=email] + form { + width: $sidebar-width-wide - $pad-wide - $sidebar-pad-wide*2 - 58px; + } + } +} +.no-placeholder { + body > nav .search { + background: lighten($nav-bg, 15) image-url('search.png') .3em .25em no-repeat; + text-indent: 1.3em; + } +} +@mixin mask-subscription-nav($feed: 'rss.png'){ + position: relative; top: 0px; + text-indent: -999999em; + background-color: $nav-border-right; + border: 0; + padding: 0; + &,&:after { @include mask-image($feed); } + &:after { + content: ""; + position: absolute; top: -1px; left: 0; + background-color: lighten($nav-color, 25); + } + &:hover:after { background-color: lighten($nav-color, 20); } +} +.maskImage { + body > nav { + @media only screen and (min-width: 550px) { + ul[data-subscription$=email] + form { + width: $sidebar-width-medium - $pad-medium*2 - $sidebar-pad-medium - 32px; + } + } + @media only screen and (min-width: 992px) { + ul[data-subscription$=email] + form { + width: $sidebar-width-wide - $pad-wide - $sidebar-pad-wide*2 - 32px; + } + } + } + ul.subscription { position: relative; top: .2em; li, a { border: 0; padding: 0; }} + a[rel=subscribe-rss]{ @include mask-subscription-nav('rss.png'); } + a[rel=subscribe-email]{ @include mask-subscription-nav('email.png'); } +} + diff --git a/.themes/classic/sass/partials/_sharing.scss b/.themes/classic/sass/partials/_sharing.scss new file mode 100644 index 0000000..3eecb48 --- /dev/null +++ b/.themes/classic/sass/partials/_sharing.scss @@ -0,0 +1,8 @@ +.sharing { + p.meta + & { + padding: { top: 1em; left: 0; } + background: $img-border top left repeat-x; + } +} + +#fb-root { display: none; } diff --git a/.themes/classic/sass/partials/_sidebar.scss b/.themes/classic/sass/partials/_sidebar.scss new file mode 100644 index 0000000..cd8c1ca --- /dev/null +++ b/.themes/classic/sass/partials/_sidebar.scss @@ -0,0 +1,4 @@ +@import "sidebar/base"; +@import "sidebar/googleplus"; +@import "sidebar/pinboard"; +@import "sidebar/delicious"; diff --git a/.themes/classic/sass/partials/_syntax.scss b/.themes/classic/sass/partials/_syntax.scss new file mode 100644 index 0000000..5465286 --- /dev/null +++ b/.themes/classic/sass/partials/_syntax.scss @@ -0,0 +1,261 @@ +.highlight, html .gist .gist-file .gist-syntax .gist-highlight { + table td.code { width: 100%; } + border: 1px solid $pre-border !important; +} +.highlight .line-numbers, html .gist .gist-file .gist-syntax .highlight .line_numbers { + text-align: right; + font-size: 13px; + line-height: 1.45em; + @if $solarized == light { + background: lighten($base03, 1) $noise-bg !important; + border-right: 1px solid darken($base02, 2) !important; + @include box-shadow(lighten($base03, 2) -1px 0 inset); + text-shadow: lighten($base02, 2) 0 -1px; + } @else { + background: $base02 $noise-bg !important; + border-right: 1px solid darken($base03, 2) !important; + @include box-shadow(lighten($base02, 2) -1px 0 inset); + text-shadow: darken($base02, 10) 0 -1px; + } + span { color: $base01 !important; } + padding: .8em !important; + @include border-radius(0); +} + +figure.code, .gist-file, pre { + @include box-shadow(rgba(#000, .06) 0 0 10px); + .highlight pre { @include box-shadow(none); } +} + +.gist .highlight, figure.code .highlight { + @include selection(adjust-color($base03, $lightness: 23%, $saturation: -65%), $text-shadow: $base03 0 1px); +} +html .gist .gist-file { + margin-bottom: 1.8em; + position: relative; + border: none; + padding-top: image-height("code_bg.png") !important; + .highlight { + margin-bottom: 0; + } + .gist-syntax { + border-bottom: 0 !important; + background: none !important; + .gist-highlight { + background: $base03 !important; + } + .highlight pre { + @extend .pre-code; + padding: 0; + } + } + .gist-meta { + padding: .6em 0.8em; + border: 1px solid lighten($base02, 2) !important; + color: $base01; + font-size: .7em !important; + @if $solarized == light { + background: lighten($base03, 2) $noise-bg; + border: 1px solid $pre-border !important; + border-top: 1px solid lighten($base03, 2) !important; + } @else { + background: $base02 $noise-bg; + } + @extend .sans; + line-height: 1.5em; + a { + color: mix($base1, $base01) !important; + @include hover-link; + &:hover { color: $base1 !important; } + } + a[href*='#file'] { + position: absolute; top: 0; left:0; right:-10px; + color: #474747 !important; + @extend .code-title; + &:hover { color: $link-color !important; } + } + a[href*=raw]{ + @extend .download-source; + top: .4em; + } + } +} +pre { + background: $pre-bg $noise-bg; + @include border-radius(.4em); + @extend .mono; + border: 1px solid $pre-border; + line-height: 1.45em; + font-size: 13px; + margin-bottom: 2.1em; + padding: .8em 1em; + color: $pre-color; + overflow: auto; +} +h3.filename { + @extend .code-title; + + pre { @include border-top-radius(0px); } +} + +p, li { + code { + @extend .mono; + display: inline-block; + white-space: no-wrap; + background: #fff; + font-size: .8em; + line-height: 1.5em; + color: #555; + border: 1px solid #ddd; + @include border-radius(.4em); + padding: 0 .3em; + margin: -1px 0; + } + pre code { font-size: 1em !important; background: none; border: none; } +} + +.pre-code { + font-family: $mono !important; + overflow: scroll; + overflow-y: hidden; + display: block; + padding: .8em; + overflow-x: auto; + line-height: 1.45em; + background: $base03 $noise-bg !important; + color: $base1 !important; + span { color: $base1 !important; } + span { font-style: normal !important; font-weight: normal !important; } + + .c { color: $base01 !important; font-style: italic !important; } /* Comment */ + .cm { color: $base01 !important; font-style: italic !important; } /* Comment.Multiline */ + .cp { color: $base01 !important; font-style: italic !important; } /* Comment.Preproc */ + .c1 { color: $base01 !important; font-style: italic !important; } /* Comment.Single */ + .cs { color: $base01 !important; font-weight: bold !important; font-style: italic !important; } /* Comment.Special */ + .err { color: $solar-red !important; background: none !important; } /* Error */ + .k { color: $solar-orange !important; } /* Keyword */ + .o { color: $base1 !important; font-weight: bold !important; } /* Operator */ + .p { color: $base1 !important; } /* Operator */ + .ow { color: $solar-cyan !important; font-weight: bold !important; } /* Operator.Word */ + .gd { color: $base1 !important; background-color: mix($solar-red, $base03, 25%) !important; display: inline-block; } /* Generic.Deleted */ + .gd .x { color: $base1 !important; background-color: mix($solar-red, $base03, 35%) !important; display: inline-block; } /* Generic.Deleted.Specific */ + .ge { color: $base1 !important; font-style: italic !important; } /* Generic.Emph */ + //.gr { color: #aa0000 } /* Generic.Error */ + .gh { color: $base01 !important; } /* Generic.Heading */ + .gi { color: $base1 !important; background-color: mix($solar-green, $base03, 20%) !important; display: inline-block; } /* Generic.Inserted */ + .gi .x { color: $base1 !important; background-color: mix($solar-green, $base03, 40%) !important; display: inline-block; } /* Generic.Inserted.Specific */ + //.go { color: #888888 } /* Generic.Output */ + //.gp { color: #555555 } /* Generic.Prompt */ + .gs { color: $base1 !important; font-weight: bold !important; } /* Generic.Strong */ + .gu { color: $solar-violet !important; } /* Generic.Subheading */ + //.gt { color: #aa0000 } /* Generic.Traceback */ + .kc { color: $solar-green !important; font-weight: bold !important; } /* Keyword.Constant */ + .kd { color: $solar-blue !important; } /* Keyword.Declaration */ + .kp { color: $solar-orange !important; font-weight: bold !important; } /* Keyword.Pseudo */ + .kr { color: $solar-magenta !important; font-weight: bold !important; } /* Keyword.Reserved */ + .kt { color: $solar-cyan !important; } /* Keyword.Type */ + .n { color: $solar-blue !important; } + .na { color: $solar-blue !important; } /* Name.Attribute */ + .nb { color: $solar-green !important; } /* Name.Builtin */ + .nc { color: $solar-magenta !important;} /* Name.Class */ + .no { color: $solar-yellow !important; } /* Name.Constant */ + //.ni { color: #800080 } /* Name.Entity */ + .nl { color: $solar-green !important; } + .ne { color: $solar-blue !important; font-weight: bold !important; } /* Name.Exception */ + .nf { color: $solar-blue !important; font-weight: bold !important; } /* Name.Function */ + .nn { color: $solar-yellow !important; } /* Name.Namespace */ + .nt { color: $solar-blue !important; font-weight: bold !important; } /* Name.Tag */ + .nx { color: $solar-yellow !Important; } + //.bp { color: #999999 } /* Name.Builtin.Pseudo */ + //.vc { color: #008080 } /* Name.Variable.Class */ + .vg { color: $solar-blue !important; } /* Name.Variable.Global */ + .vi { color: $solar-blue !important; } /* Name.Variable.Instance */ + .nv { color: $solar-blue !important; } /* Name.Variable */ + //.w { color: #bbbbbb } /* Text.Whitespace */ + .mf { color: $solar-cyan !important; } /* Literal.Number.Float */ + .m { color: $solar-cyan !important; } /* Literal.Number */ + .mh { color: $solar-cyan !important; } /* Literal.Number.Hex */ + .mi { color: $solar-cyan !important; } /* Literal.Number.Integer */ + //.mo { color: #009999 } /* Literal.Number.Oct */ + .s { color: $solar-cyan !important; } /* Literal.String */ + //.sb { color: #d14 } /* Literal.String.Backtick */ + //.sc { color: #d14 } /* Literal.String.Char */ + .sd { color: $solar-cyan !important; } /* Literal.String.Doc */ + .s2 { color: $solar-cyan !important; } /* Literal.String.Double */ + .se { color: $solar-red !important; } /* Literal.String.Escape */ + //.sh { color: #d14 } /* Literal.String.Heredoc */ + .si { color: $solar-blue !important; } /* Literal.String.Interpol */ + //.sx { color: #d14 } /* Literal.String.Other */ + .sr { color: $solar-cyan !important; } /* Literal.String.Regex */ + .s1 { color: $solar-cyan !important; } /* Literal.String.Single */ + //.ss { color: #990073 } /* Literal.String.Symbol */ + //.il { color: #009999 } /* Literal.Number.Integer.Long */ + div { .gd, .gd .x, .gi, .gi .x { display: inline-block; width: 100%; }} +} + +.highlight, .gist-highlight { + pre { background: none; @include border-radius(0px); border: none; padding: 0; margin-bottom: 0; } + margin-bottom: 1.8em; + background: $base03; + overflow-y: hidden; + overflow-x: auto; +} + +$solar-scroll-bg: rgba(#fff, .15); +$solar-scroll-thumb: rgba(#fff, .2); +@if $solarized == light { + $solar-scroll-bg: rgba(#000, .15); + $solar-scroll-thumb: rgba(#000, .15); +} + +pre, .highlight, .gist-highlight { + &::-webkit-scrollbar { height: .5em; background: $solar-scroll-bg; } + &::-webkit-scrollbar-thumb:horizontal { background: $solar-scroll-thumb; -webkit-border-radius: 4px; border-radius: 4px } +} + +.highlight code { + @extend .pre-code; background: #000; +} +figure.code { + background: none; + padding: 0; + border: 0; + margin-bottom: 1.5em; + pre { margin-bottom: 0; } + figcaption { + position: relative; + @extend .code-title; + a { @extend .download-source; } + } + .highlight { + margin-bottom: 0; + } +} + +.code-title { + text-align: center; + font-size: 13px; + line-height: 2em; + text-shadow: #cbcccc 0 1px 0; + color: #474747; + font-weight: normal; + margin-bottom: 0; + @include border-top-radius(5px); + font-family: "Helvetica Neue", Arial, "Lucida Grande", "Lucida Sans Unicode", Lucida, sans-serif; + background: #aaaaaa image-url("code_bg.png") top repeat-x; + border: 1px solid #565656; + border-top-color: #cbcbcb; + border-left-color: #a5a5a5; + border-right-color: #a5a5a5; + border-bottom: 0; +} + +.download-source { + position: absolute; right: .8em; + @include hover-link; + color: #666 !important; + z-index: 1; + font-size: 13px; + text-shadow: #cbcccc 0 1px 0; + padding-left: 3em; +} diff --git a/.themes/classic/sass/partials/sidebar/_base.scss b/.themes/classic/sass/partials/sidebar/_base.scss new file mode 100644 index 0000000..5441304 --- /dev/null +++ b/.themes/classic/sass/partials/sidebar/_base.scss @@ -0,0 +1,106 @@ +.side-shadow-border { + @include box-shadow(lighten($sidebar-bg, 5) 0 1px); +} +aside.sidebar { + overflow: hidden; + color: $sidebar-color; + text-shadow: lighten($sidebar-bg, 8) 0 1px; + a { @extend .force-wrap; } + section { + @extend .sans; + font-size: .8em; + line-height: 1.4em; + margin-bottom: 1.5em; + h1 { + margin: 1.5em 0 0; + padding-bottom: .2em; + border-bottom: 1px solid $sidebar-border; + @extend .side-shadow-border; + + p { + padding-top: .4em; + } + } + } + img { + @extend .flex-content; + @extend .basic-alignment; + @include shadow-box($border: #fff .3em solid); + } + ul { + margin-bottom: 0.5em; + margin-left: 0; + } + li { + list-style: none; + padding: .5em 0; + margin: 0; + border-bottom: 1px solid $sidebar-border; + @extend .side-shadow-border; + p:last-child { + margin-bottom: 0; + } + } + a { + color: inherit; + @include transition(color .5s); + } + &:hover a { + color: $sidebar-link-color; + &:hover { color: $sidebar-link-color-hover; } + } +} +.aside-alt-link { + color: $sidebar-link-color-subdued; + &:hover { + color: $sidebar-link-color-subdued-hover; + } +} + +@media only screen and (min-width: 768px) { + .toggle-sidebar { + outline: none; + position: absolute; right: -10px; top: 0; bottom: 0; + display: inline-block; + text-decoration: none; + color: mix($text-color-light, $sidebar-bg); + width: 9px; + cursor: pointer; + &:hover { + background: mix($sidebar-border, $sidebar-bg); + @include background(linear-gradient(left, rgba($sidebar-border, .5), rgba($sidebar-border, 0))); + } + &:after { + position: absolute; right: -11px; top: 0; + width: 20px; + font-size: 1.2em; + line-height: 1.1em; + padding-bottom: .15em; + @include border-bottom-right-radius(.3em); + text-align: center; + background: $main-bg $noise-bg; + border-bottom: 1px solid $sidebar-border; + border-right: 1px solid $sidebar-border; + content: "\00BB"; + text-indent: -1px; + } + .collapse-sidebar & { + text-indent: 0px; + right: -20px; + width: 19px; + &:hover { + background: mix($sidebar-border, $sidebar-bg); + } + &:after { + border-left: 1px solid $sidebar-border; + text-shadow: #fff 0 1px; + content: "\00AB"; + left: 0px; right: 0; + text-align: center; + text-indent: 0; + border: 0; + border-right-width: 0; + background: none; + } + } + } +} diff --git a/.themes/classic/sass/partials/sidebar/_delicious.scss b/.themes/classic/sass/partials/sidebar/_delicious.scss new file mode 100644 index 0000000..e962702 --- /dev/null +++ b/.themes/classic/sass/partials/sidebar/_delicious.scss @@ -0,0 +1,4 @@ +.delicious-posts { + a.delicious-link { margin-bottom: .5em; display: block; } + p { font-size: 1em; } +} diff --git a/.themes/classic/sass/partials/sidebar/_googleplus.scss b/.themes/classic/sass/partials/sidebar/_googleplus.scss new file mode 100644 index 0000000..c2a693e --- /dev/null +++ b/.themes/classic/sass/partials/sidebar/_googleplus.scss @@ -0,0 +1,26 @@ +.googleplus { + h1 { + -moz-box-shadow: none !important; + -webkit-box-shadow: none !important; + -o-box-shadow: none !important; + box-shadow: none !important; + border-bottom: 0px none !important; + } + a { + text-decoration: none; + white-space: normal !important; + line-height: 32px; + + img { + float: left; + margin-right: 0.5em; + border: 0 none; + } + } +} + +.googleplus-hidden { + position: absolute; + top: -1000em; + left: -1000em; +} diff --git a/.themes/classic/sass/partials/sidebar/_pinboard.scss b/.themes/classic/sass/partials/sidebar/_pinboard.scss new file mode 100644 index 0000000..9f9ab46 --- /dev/null +++ b/.themes/classic/sass/partials/sidebar/_pinboard.scss @@ -0,0 +1,12 @@ +#pinboard_linkroll { + .pin-title, .pin-description { + display: block; + margin-bottom: .5em; + } + .pin-tag { + @include hover-link; + @extend .aside-alt-link; + &:after { content: ','; } + &:last-child:after { content: ''; } + } +} diff --git a/.themes/classic/sass/plugins/_plugins.scss b/.themes/classic/sass/plugins/_plugins.scss new file mode 100644 index 0000000..3b4ba0b --- /dev/null +++ b/.themes/classic/sass/plugins/_plugins.scss @@ -0,0 +1,6 @@ +/* + Add plugin stylesheets to this directory and they will be automatically + Imported. Load order is alphabetical and styles can be overriden in + custom/_style.scss which is loaded after all plugin stylesheets. +*/ + diff --git a/.themes/classic/sass/screen.scss b/.themes/classic/sass/screen.scss new file mode 100644 index 0000000..75eac0a --- /dev/null +++ b/.themes/classic/sass/screen.scss @@ -0,0 +1,10 @@ +@import "compass"; +@include global-reset; + +@import "custom/colors"; +@import "custom/fonts"; +@import "custom/layout"; +@import "base"; +@import "partials"; +@import "plugins/**/*"; +@import "custom/styles"; diff --git a/.themes/classic/source/_includes/after_footer.html b/.themes/classic/source/_includes/after_footer.html new file mode 100644 index 0000000..08b8e34 --- /dev/null +++ b/.themes/classic/source/_includes/after_footer.html @@ -0,0 +1,5 @@ +{% include disqus.html %} +{% include facebook_like.html %} +{% include google_plus_one.html %} +{% include twitter_sharing.html %} +{% include custom/after_footer.html %} diff --git a/.themes/classic/source/_includes/archive_post.html b/.themes/classic/source/_includes/archive_post.html new file mode 100644 index 0000000..210dbbb --- /dev/null +++ b/.themes/classic/source/_includes/archive_post.html @@ -0,0 +1,8 @@ +{% capture category %}{{ post.categories | size }}{% endcapture %} +

{% if site.titlecase %}{{ post.title | titlecase }}{% else %}{{ post.title }}{% endif %}

+ +{% if category != '0' %} + +{% endif %} diff --git a/.themes/classic/source/_includes/article.html b/.themes/classic/source/_includes/article.html new file mode 100644 index 0000000..8427997 --- /dev/null +++ b/.themes/classic/source/_includes/article.html @@ -0,0 +1,29 @@ +{% unless page.no_header %} +
+ {% if index %} +

{% if site.titlecase %}{{ post.title | titlecase }}{% else %}{{ post.title }}{% endif %}

+ {% else %} +

{% if site.titlecase %}{{ page.title | titlecase }}{% else %}{{ page.title }}{% endif %}

+ {% endif %} + {% unless page.meta == false %} +

+ {% include post/date.html %}{{ time }} + {% if site.disqus_short_name and page.comments != false and post.comments != false and site.disqus_show_comment_count == true %} + | Comments + {% endif %} +

+ {% endunless %} +
+{% endunless %} +{% if index %} +
{{ content | excerpt }}
+ {% capture excerpted %}{{ content | has_excerpt }}{% endcapture %} + {% if excerpted == 'true' %} + + {% endif %} +{% else %} +
{{ content }}
+{% endif %} diff --git a/.themes/classic/source/_includes/asides/delicious.html b/.themes/classic/source/_includes/asides/delicious.html new file mode 100644 index 0000000..115cdcb --- /dev/null +++ b/.themes/classic/source/_includes/asides/delicious.html @@ -0,0 +1,8 @@ +{% if site.delicious_user %} +
+

On Delicious

+
+ +

My Delicious Bookmarks »

+
+{% endif %} \ No newline at end of file diff --git a/.themes/classic/source/_includes/asides/github.html b/.themes/classic/source/_includes/asides/github.html new file mode 100644 index 0000000..b0bf959 --- /dev/null +++ b/.themes/classic/source/_includes/asides/github.html @@ -0,0 +1,30 @@ +{% if site.github_user %} +
+

GitHub Repos

+ + {% if site.github_show_profile_link %} + @{{site.github_user}} on GitHub + {% endif %} + + +
+{% endif %} diff --git a/.themes/classic/source/_includes/asides/googleplus.html b/.themes/classic/source/_includes/asides/googleplus.html new file mode 100644 index 0000000..00a0aa8 --- /dev/null +++ b/.themes/classic/source/_includes/asides/googleplus.html @@ -0,0 +1,11 @@ +{% if site.googleplus_user %} +
+

+ + + Google+ + +

+
+{% endif %} + diff --git a/.themes/classic/source/_includes/asides/pinboard.html b/.themes/classic/source/_includes/asides/pinboard.html new file mode 100644 index 0000000..c89c3e7 --- /dev/null +++ b/.themes/classic/source/_includes/asides/pinboard.html @@ -0,0 +1,19 @@ +{% if site.pinboard_user %} +
+

My Pinboard

+ +

My Pinboard Bookmarks »

+
+ +{% endif %} diff --git a/.themes/classic/source/_includes/asides/recent_posts.html b/.themes/classic/source/_includes/asides/recent_posts.html new file mode 100644 index 0000000..4b10847 --- /dev/null +++ b/.themes/classic/source/_includes/asides/recent_posts.html @@ -0,0 +1,10 @@ +
+

Recent Posts

+ +
diff --git a/.themes/classic/source/_includes/custom/after_footer.html b/.themes/classic/source/_includes/custom/after_footer.html new file mode 100644 index 0000000..bce25dd --- /dev/null +++ b/.themes/classic/source/_includes/custom/after_footer.html @@ -0,0 +1,3 @@ +{% comment %} + Add content to be output at the bottom of each page. (You might use this for analytics scripts, for example) +{% endcomment %} diff --git a/.themes/classic/source/_includes/custom/asides/about.html b/.themes/classic/source/_includes/custom/asides/about.html new file mode 100644 index 0000000..59d309e --- /dev/null +++ b/.themes/classic/source/_includes/custom/asides/about.html @@ -0,0 +1,4 @@ +
+

About Me

+

A little something about me.

+
diff --git a/.themes/classic/source/_includes/custom/category_feed.xml b/.themes/classic/source/_includes/custom/category_feed.xml new file mode 100644 index 0000000..2f16eee --- /dev/null +++ b/.themes/classic/source/_includes/custom/category_feed.xml @@ -0,0 +1,27 @@ +--- +layout: nil +--- + + + + <![CDATA[{% if site.titlecase %}{{ page.title | titlecase | cdata_escape }}{% else %}{{ page.title | cdata_escape }}{% endif %} | {{ site.title | cdata_escape }}]]> + + + {{ site.time | date_to_xmlschema }} + {{ site.url }}/ + + + {% if site.email %}{% endif %} + + Octopress + + {% for post in site.categories[page.category] limit: 5 %} + + <![CDATA[{% if site.titlecase %}{{ post.title | titlecase | cdata_escape }}{% else %}{{ post.title | cdata_escape }}{% endif %}]]> + + {{ post.date | date_to_xmlschema }} + {{ site.url }}{{ post.id }} + + + {% endfor %} + diff --git a/.themes/classic/source/_includes/custom/footer.html b/.themes/classic/source/_includes/custom/footer.html new file mode 100644 index 0000000..e12f067 --- /dev/null +++ b/.themes/classic/source/_includes/custom/footer.html @@ -0,0 +1,4 @@ +

+ Copyright © {{ site.time | date: "%Y" }} - {{ site.author }} - + Powered by Octopress +

diff --git a/.themes/classic/source/_includes/custom/head.html b/.themes/classic/source/_includes/custom/head.html new file mode 100644 index 0000000..85879f4 --- /dev/null +++ b/.themes/classic/source/_includes/custom/head.html @@ -0,0 +1,3 @@ + + + diff --git a/.themes/classic/source/_includes/custom/header.html b/.themes/classic/source/_includes/custom/header.html new file mode 100644 index 0000000..35f9c05 --- /dev/null +++ b/.themes/classic/source/_includes/custom/header.html @@ -0,0 +1,6 @@ +
+

{{ site.title }}

+ {% if site.subtitle %} +

{{ site.subtitle }}

+ {% endif %} +
diff --git a/.themes/classic/source/_includes/custom/navigation.html b/.themes/classic/source/_includes/custom/navigation.html new file mode 100644 index 0000000..d6bd424 --- /dev/null +++ b/.themes/classic/source/_includes/custom/navigation.html @@ -0,0 +1,4 @@ + diff --git a/.themes/classic/source/_includes/disqus.html b/.themes/classic/source/_includes/disqus.html new file mode 100644 index 0000000..49c6a29 --- /dev/null +++ b/.themes/classic/source/_includes/disqus.html @@ -0,0 +1,21 @@ +{% comment %} Load script if disquss comments are enabled and `page.comments` is either empty (index) or set to true {% endcomment %} +{% if site.disqus_short_name and page.comments != false %} + +{% endif %} diff --git a/.themes/classic/source/_includes/facebook_like.html b/.themes/classic/source/_includes/facebook_like.html new file mode 100644 index 0000000..d263e6d --- /dev/null +++ b/.themes/classic/source/_includes/facebook_like.html @@ -0,0 +1,10 @@ +{% if site.facebook_like %} +
+ +{% endif %} diff --git a/.themes/classic/source/_includes/footer.html b/.themes/classic/source/_includes/footer.html new file mode 100644 index 0000000..3a8c768 --- /dev/null +++ b/.themes/classic/source/_includes/footer.html @@ -0,0 +1 @@ +{% include custom/footer.html %} diff --git a/.themes/classic/source/_includes/google_analytics.html b/.themes/classic/source/_includes/google_analytics.html new file mode 100644 index 0000000..4d4d596 --- /dev/null +++ b/.themes/classic/source/_includes/google_analytics.html @@ -0,0 +1,13 @@ +{% if site.google_analytics_tracking_id %} + +{% endif %} diff --git a/.themes/classic/source/_includes/google_plus_one.html b/.themes/classic/source/_includes/google_plus_one.html new file mode 100644 index 0000000..b69ddae --- /dev/null +++ b/.themes/classic/source/_includes/google_plus_one.html @@ -0,0 +1,9 @@ +{% if site.google_plus_one %} + +{% endif %} diff --git a/.themes/classic/source/_includes/head.html b/.themes/classic/source/_includes/head.html new file mode 100644 index 0000000..6b5412c --- /dev/null +++ b/.themes/classic/source/_includes/head.html @@ -0,0 +1,30 @@ + + + + + + + {% if page.title %}{% if site.titlecase %}{{ page.title | titlecase }}{% else %}{{ page.title }}{% endif %} - {% endif %}{{ site.title }} + + + {% capture description %}{% if page.description %}{{ page.description }}{% else %}{{ content | raw_content }}{% endif %}{% endcapture %} + + {% if page.keywords %}{% endif %} + + + + + + + {% capture canonical %}{{ site.url }}{% if site.permalink contains '.html' %}{{ page.url }}{% else %}{{ page.url | remove:'index.html' | strip_slash }}{% endif %}{% endcapture %} + + + + + + + + + {% include custom/head.html %} + {% include google_analytics.html %} + diff --git a/.themes/classic/source/_includes/header.html b/.themes/classic/source/_includes/header.html new file mode 100644 index 0000000..524de65 --- /dev/null +++ b/.themes/classic/source/_includes/header.html @@ -0,0 +1 @@ +{% include custom/header.html %} diff --git a/.themes/classic/source/_includes/navigation.html b/.themes/classic/source/_includes/navigation.html new file mode 100644 index 0000000..2f0e628 --- /dev/null +++ b/.themes/classic/source/_includes/navigation.html @@ -0,0 +1,15 @@ + + {% if site.simple_search %} +
+
+ + +
+
+ {% endif %} +{% include custom/navigation.html %} diff --git a/.themes/classic/source/_includes/post/author.html b/.themes/classic/source/_includes/post/author.html new file mode 100644 index 0000000..83dd6a8 --- /dev/null +++ b/.themes/classic/source/_includes/post/author.html @@ -0,0 +1,8 @@ +{% if post.author %} + {% assign author = post.author %} +{% elsif page.author %} + {% assign author = page.author %} +{% else %} + {% assign author = site.author %} +{% endif %} +{% if author %}Posted by {{ author }}{% endif %} diff --git a/.themes/classic/source/_includes/post/categories.html b/.themes/classic/source/_includes/post/categories.html new file mode 100644 index 0000000..4a98b29 --- /dev/null +++ b/.themes/classic/source/_includes/post/categories.html @@ -0,0 +1,10 @@ +{% capture category %}{% if post %}{{ post.categories | category_links | size }}{% else %}{{ page.categories | category_links | size }}{% endif %}{% endcapture %} +{% unless category == '0' %} + + {% if post %} + {{ post.categories | category_links }} + {% else %} + {{ page.categories | category_links }} + {% endif %} + +{% endunless %} diff --git a/.themes/classic/source/_includes/post/date.html b/.themes/classic/source/_includes/post/date.html new file mode 100644 index 0000000..ecf1ad7 --- /dev/null +++ b/.themes/classic/source/_includes/post/date.html @@ -0,0 +1,15 @@ +{% capture date %}{{ page.date }}{{ post.date }}{% endcapture %} +{% capture date_formatted %}{{ page.date_formatted }}{{ post.date_formatted }}{% endcapture %} +{% capture has_date %}{{ date | size }}{% endcapture %} + +{% capture updated %}{{ page.updated }}{{ post.updated }}{% endcapture %} +{% capture updated_formatted %}{{ page.updated_formatted }}{{ post.updated_formatted }}{% endcapture %} +{% capture was_updated %}{{ updated | size }}{% endcapture %} + +{% if has_date != '0' %} + {% capture time %}{% endcapture %} +{% endif %} + +{% if was_updated != '0' %} + {% capture updated %}{% endcapture %} +{% else %}{% assign updated = false %}{% endif %} \ No newline at end of file diff --git a/.themes/classic/source/_includes/post/disqus_thread.html b/.themes/classic/source/_includes/post/disqus_thread.html new file mode 100644 index 0000000..b1acd8c --- /dev/null +++ b/.themes/classic/source/_includes/post/disqus_thread.html @@ -0,0 +1 @@ + diff --git a/.themes/classic/source/_includes/post/sharing.html b/.themes/classic/source/_includes/post/sharing.html new file mode 100644 index 0000000..d639faa --- /dev/null +++ b/.themes/classic/source/_includes/post/sharing.html @@ -0,0 +1,11 @@ +
+ {% if site.twitter_tweet_button %} + Tweet + {% endif %} + {% if site.google_plus_one %} +
+ {% endif %} + {% if site.facebook_like %} +
+ {% endif %} +
diff --git a/.themes/classic/source/_includes/twitter_sharing.html b/.themes/classic/source/_includes/twitter_sharing.html new file mode 100644 index 0000000..7cd5fbc --- /dev/null +++ b/.themes/classic/source/_includes/twitter_sharing.html @@ -0,0 +1,11 @@ +{% if site.twitter_follow_button or site.twitter_tweet_button %} + +{% endif %} diff --git a/.themes/classic/source/_layouts/category_index.html b/.themes/classic/source/_layouts/category_index.html new file mode 100644 index 0000000..85a6307 --- /dev/null +++ b/.themes/classic/source/_layouts/category_index.html @@ -0,0 +1,17 @@ +--- +layout: page +footer: false +--- + +
+{% for post in site.categories[page.category] %} +{% capture this_year %}{{ post.date | date: "%Y" }}{% endcapture %} +{% unless year == this_year %} + {% assign year = this_year %} +

{{ year }}

+{% endunless %} +
+ {% include archive_post.html %} +
+{% endfor %} +
diff --git a/.themes/classic/source/_layouts/default.html b/.themes/classic/source/_layouts/default.html new file mode 100644 index 0000000..f23b07b --- /dev/null +++ b/.themes/classic/source/_layouts/default.html @@ -0,0 +1,14 @@ +{% capture root_url %}{{ site.root | strip_slash }}{% endcapture %} +{% include head.html %} + +
{% include header.html %}
+ +
+
+ {{ content | expand_urls: root_url }} +
+
+ + {% include after_footer.html %} + + diff --git a/.themes/classic/source/_layouts/page.html b/.themes/classic/source/_layouts/page.html new file mode 100644 index 0000000..8ba6ec9 --- /dev/null +++ b/.themes/classic/source/_layouts/page.html @@ -0,0 +1,42 @@ +--- +layout: default +--- + +
+
+ {% if page.title %} +
+

{% if site.titlecase %}{{ page.title | titlecase }}{% else %}{{ page.title }}{% endif %}

+ {% if page.date %}

{% include post/date.html %}{{ time }}

{% endif %} +
+ {% endif %} + {{ content }} + {% unless page.footer == false %} +
+ {% if page.date or page.author %}

+ {% if page.author %}{% include post/author.html %}{% endif %} + {% include post/date.html %}{% if updated %}{{ updated }}{% else %}{{ time }}{% endif %} + {% if page.categories %}{% include post/categories.html %}{% endif %} +

{% endif %} + {% unless page.sharing == false %} + {% include post/sharing.html %} + {% endunless %} +
+ {% endunless %} +
+{% if site.disqus_short_name and page.comments == true %} +
+

Comments

+
{% include post/disqus_thread.html %}
+
+{% endif %} +
+{% unless page.sidebar == false %} + +{% endunless %} diff --git a/.themes/classic/source/_layouts/post.html b/.themes/classic/source/_layouts/post.html new file mode 100644 index 0000000..4091168 --- /dev/null +++ b/.themes/classic/source/_layouts/post.html @@ -0,0 +1,43 @@ +--- +layout: default +single: true +--- + +
+
+ {% include article.html %} +
+

+ {% include post/author.html %} + {% include post/date.html %}{% if updated %}{{ updated }}{% else %}{{ time }}{% endif %} + {% include post/categories.html %} +

+ {% unless page.sharing == false %} + {% include post/sharing.html %} + {% endunless %} +

+ {% if page.previous.url %} + « {{page.previous.title}} + {% endif %} + {% if page.next.url %} + {{page.next.title}} » + {% endif %} +

+
+
+{% if site.disqus_short_name and page.comments == true %} +
+

Comments

+
{% include post/disqus_thread.html %}
+
+{% endif %} +
+{% unless page.sidebar == false %} + +{% endunless %} diff --git a/assets/jwplayer/glow/controlbar/background.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/background.png similarity index 100% rename from assets/jwplayer/glow/controlbar/background.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/background.png diff --git a/assets/jwplayer/glow/controlbar/blankButton.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/blankButton.png similarity index 100% rename from assets/jwplayer/glow/controlbar/blankButton.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/blankButton.png diff --git a/assets/jwplayer/glow/controlbar/divider.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/divider.png similarity index 100% rename from assets/jwplayer/glow/controlbar/divider.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/divider.png diff --git a/assets/jwplayer/glow/controlbar/fullscreenButton.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/fullscreenButton.png similarity index 100% rename from assets/jwplayer/glow/controlbar/fullscreenButton.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/fullscreenButton.png diff --git a/assets/jwplayer/glow/controlbar/fullscreenButtonOver.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/fullscreenButtonOver.png similarity index 100% rename from assets/jwplayer/glow/controlbar/fullscreenButtonOver.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/fullscreenButtonOver.png diff --git a/assets/jwplayer/glow/controlbar/muteButton.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/muteButton.png similarity index 100% rename from assets/jwplayer/glow/controlbar/muteButton.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/muteButton.png diff --git a/assets/jwplayer/glow/controlbar/muteButtonOver.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/muteButtonOver.png similarity index 100% rename from assets/jwplayer/glow/controlbar/muteButtonOver.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/muteButtonOver.png diff --git a/assets/jwplayer/glow/controlbar/normalscreenButton.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/normalscreenButton.png similarity index 100% rename from assets/jwplayer/glow/controlbar/normalscreenButton.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/normalscreenButton.png diff --git a/assets/jwplayer/glow/controlbar/normalscreenButtonOver.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/normalscreenButtonOver.png similarity index 100% rename from assets/jwplayer/glow/controlbar/normalscreenButtonOver.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/normalscreenButtonOver.png diff --git a/assets/jwplayer/glow/controlbar/pauseButton.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/pauseButton.png similarity index 100% rename from assets/jwplayer/glow/controlbar/pauseButton.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/pauseButton.png diff --git a/assets/jwplayer/glow/controlbar/pauseButtonOver.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/pauseButtonOver.png similarity index 100% rename from assets/jwplayer/glow/controlbar/pauseButtonOver.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/pauseButtonOver.png diff --git a/assets/jwplayer/glow/controlbar/playButton.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/playButton.png similarity index 100% rename from assets/jwplayer/glow/controlbar/playButton.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/playButton.png diff --git a/assets/jwplayer/glow/controlbar/playButtonOver.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/playButtonOver.png similarity index 100% rename from assets/jwplayer/glow/controlbar/playButtonOver.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/playButtonOver.png diff --git a/assets/jwplayer/glow/controlbar/timeSliderBuffer.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/timeSliderBuffer.png similarity index 100% rename from assets/jwplayer/glow/controlbar/timeSliderBuffer.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/timeSliderBuffer.png diff --git a/assets/jwplayer/glow/controlbar/timeSliderCapLeft.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/timeSliderCapLeft.png similarity index 100% rename from assets/jwplayer/glow/controlbar/timeSliderCapLeft.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/timeSliderCapLeft.png diff --git a/assets/jwplayer/glow/controlbar/timeSliderCapRight.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/timeSliderCapRight.png similarity index 100% rename from assets/jwplayer/glow/controlbar/timeSliderCapRight.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/timeSliderCapRight.png diff --git a/assets/jwplayer/glow/controlbar/timeSliderProgress.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/timeSliderProgress.png similarity index 100% rename from assets/jwplayer/glow/controlbar/timeSliderProgress.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/timeSliderProgress.png diff --git a/assets/jwplayer/glow/controlbar/timeSliderRail.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/timeSliderRail.png similarity index 100% rename from assets/jwplayer/glow/controlbar/timeSliderRail.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/timeSliderRail.png diff --git a/assets/jwplayer/glow/controlbar/unmuteButton.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/unmuteButton.png similarity index 100% rename from assets/jwplayer/glow/controlbar/unmuteButton.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/unmuteButton.png diff --git a/assets/jwplayer/glow/controlbar/unmuteButtonOver.png b/.themes/classic/source/assets/jwplayer/glow/controlbar/unmuteButtonOver.png similarity index 100% rename from assets/jwplayer/glow/controlbar/unmuteButtonOver.png rename to .themes/classic/source/assets/jwplayer/glow/controlbar/unmuteButtonOver.png diff --git a/assets/jwplayer/glow/display/background.png b/.themes/classic/source/assets/jwplayer/glow/display/background.png similarity index 100% rename from assets/jwplayer/glow/display/background.png rename to .themes/classic/source/assets/jwplayer/glow/display/background.png diff --git a/assets/jwplayer/glow/display/bufferIcon.png b/.themes/classic/source/assets/jwplayer/glow/display/bufferIcon.png similarity index 100% rename from assets/jwplayer/glow/display/bufferIcon.png rename to .themes/classic/source/assets/jwplayer/glow/display/bufferIcon.png diff --git a/assets/jwplayer/glow/display/muteIcon.png b/.themes/classic/source/assets/jwplayer/glow/display/muteIcon.png similarity index 100% rename from assets/jwplayer/glow/display/muteIcon.png rename to .themes/classic/source/assets/jwplayer/glow/display/muteIcon.png diff --git a/assets/jwplayer/glow/display/playIcon.png b/.themes/classic/source/assets/jwplayer/glow/display/playIcon.png similarity index 100% rename from assets/jwplayer/glow/display/playIcon.png rename to .themes/classic/source/assets/jwplayer/glow/display/playIcon.png diff --git a/assets/jwplayer/glow/dock/button.png b/.themes/classic/source/assets/jwplayer/glow/dock/button.png similarity index 100% rename from assets/jwplayer/glow/dock/button.png rename to .themes/classic/source/assets/jwplayer/glow/dock/button.png diff --git a/assets/jwplayer/glow/glow.xml b/.themes/classic/source/assets/jwplayer/glow/glow.xml similarity index 100% rename from assets/jwplayer/glow/glow.xml rename to .themes/classic/source/assets/jwplayer/glow/glow.xml diff --git a/assets/jwplayer/glow/playlist/item.png b/.themes/classic/source/assets/jwplayer/glow/playlist/item.png similarity index 100% rename from assets/jwplayer/glow/playlist/item.png rename to .themes/classic/source/assets/jwplayer/glow/playlist/item.png diff --git a/assets/jwplayer/glow/playlist/itemOver.png b/.themes/classic/source/assets/jwplayer/glow/playlist/itemOver.png similarity index 100% rename from assets/jwplayer/glow/playlist/itemOver.png rename to .themes/classic/source/assets/jwplayer/glow/playlist/itemOver.png diff --git a/assets/jwplayer/glow/playlist/sliderCapBottom.png b/.themes/classic/source/assets/jwplayer/glow/playlist/sliderCapBottom.png similarity index 100% rename from assets/jwplayer/glow/playlist/sliderCapBottom.png rename to .themes/classic/source/assets/jwplayer/glow/playlist/sliderCapBottom.png diff --git a/assets/jwplayer/glow/playlist/sliderCapTop.png b/.themes/classic/source/assets/jwplayer/glow/playlist/sliderCapTop.png similarity index 100% rename from assets/jwplayer/glow/playlist/sliderCapTop.png rename to .themes/classic/source/assets/jwplayer/glow/playlist/sliderCapTop.png diff --git a/assets/jwplayer/glow/playlist/sliderRail.png b/.themes/classic/source/assets/jwplayer/glow/playlist/sliderRail.png similarity index 100% rename from assets/jwplayer/glow/playlist/sliderRail.png rename to .themes/classic/source/assets/jwplayer/glow/playlist/sliderRail.png diff --git a/assets/jwplayer/glow/playlist/sliderThumb.png b/.themes/classic/source/assets/jwplayer/glow/playlist/sliderThumb.png similarity index 100% rename from assets/jwplayer/glow/playlist/sliderThumb.png rename to .themes/classic/source/assets/jwplayer/glow/playlist/sliderThumb.png diff --git a/assets/jwplayer/glow/sharing/embedIcon.png b/.themes/classic/source/assets/jwplayer/glow/sharing/embedIcon.png similarity index 100% rename from assets/jwplayer/glow/sharing/embedIcon.png rename to .themes/classic/source/assets/jwplayer/glow/sharing/embedIcon.png diff --git a/assets/jwplayer/glow/sharing/embedScreen.png b/.themes/classic/source/assets/jwplayer/glow/sharing/embedScreen.png similarity index 100% rename from assets/jwplayer/glow/sharing/embedScreen.png rename to .themes/classic/source/assets/jwplayer/glow/sharing/embedScreen.png diff --git a/assets/jwplayer/glow/sharing/shareIcon.png b/.themes/classic/source/assets/jwplayer/glow/sharing/shareIcon.png similarity index 100% rename from assets/jwplayer/glow/sharing/shareIcon.png rename to .themes/classic/source/assets/jwplayer/glow/sharing/shareIcon.png diff --git a/assets/jwplayer/glow/sharing/shareScreen.png b/.themes/classic/source/assets/jwplayer/glow/sharing/shareScreen.png similarity index 100% rename from assets/jwplayer/glow/sharing/shareScreen.png rename to .themes/classic/source/assets/jwplayer/glow/sharing/shareScreen.png diff --git a/assets/jwplayer/player.swf b/.themes/classic/source/assets/jwplayer/player.swf similarity index 100% rename from assets/jwplayer/player.swf rename to .themes/classic/source/assets/jwplayer/player.swf diff --git a/.themes/classic/source/atom.xml b/.themes/classic/source/atom.xml new file mode 100644 index 0000000..d4a4427 --- /dev/null +++ b/.themes/classic/source/atom.xml @@ -0,0 +1,27 @@ +--- +layout: nil +--- + + + + <![CDATA[{{ site.title | cdata_escape }}]]> + + + {{ site.time | date_to_xmlschema }} + {{ site.url }}/ + + + {% if site.email %}{% endif %} + + Octopress + + {% for post in site.posts limit: 20 %} + + <![CDATA[{% if site.titlecase %}{{ post.title | titlecase | cdata_escape }}{% else %}{{ post.title | cdata_escape }}{% endif %}]]> + + {{ post.date | date_to_xmlschema }} + {{ site.url }}{{ post.id }} + + + {% endfor %} + diff --git a/.themes/classic/source/blog/archives/index.html b/.themes/classic/source/blog/archives/index.html new file mode 100644 index 0000000..f1d9cee --- /dev/null +++ b/.themes/classic/source/blog/archives/index.html @@ -0,0 +1,18 @@ +--- +layout: page +title: Blog Archive +footer: false +--- + +
+{% for post in site.posts reverse %} +{% capture this_year %}{{ post.date | date: "%Y" }}{% endcapture %} +{% unless year == this_year %} + {% assign year = this_year %} +

{{ year }}

+{% endunless %} +
+ {% include archive_post.html %} +
+{% endfor %} +
diff --git a/.themes/classic/source/favicon.png b/.themes/classic/source/favicon.png new file mode 100644 index 0000000..0f25067 Binary files /dev/null and b/.themes/classic/source/favicon.png differ diff --git a/images/bird_32_gray.png b/.themes/classic/source/images/bird_32_gray.png similarity index 100% rename from images/bird_32_gray.png rename to .themes/classic/source/images/bird_32_gray.png diff --git a/images/bird_32_gray_fail.png b/.themes/classic/source/images/bird_32_gray_fail.png similarity index 100% rename from images/bird_32_gray_fail.png rename to .themes/classic/source/images/bird_32_gray_fail.png diff --git a/images/code_bg.png b/.themes/classic/source/images/code_bg.png similarity index 100% rename from images/code_bg.png rename to .themes/classic/source/images/code_bg.png diff --git a/images/dotted-border.png b/.themes/classic/source/images/dotted-border.png similarity index 100% rename from images/dotted-border.png rename to .themes/classic/source/images/dotted-border.png diff --git a/images/email.png b/.themes/classic/source/images/email.png similarity index 100% rename from images/email.png rename to .themes/classic/source/images/email.png diff --git a/images/line-tile.png b/.themes/classic/source/images/line-tile.png similarity index 100% rename from images/line-tile.png rename to .themes/classic/source/images/line-tile.png diff --git a/images/noise.png b/.themes/classic/source/images/noise.png similarity index 100% rename from images/noise.png rename to .themes/classic/source/images/noise.png diff --git a/images/rss.png b/.themes/classic/source/images/rss.png similarity index 100% rename from images/rss.png rename to .themes/classic/source/images/rss.png diff --git a/images/search.png b/.themes/classic/source/images/search.png similarity index 100% rename from images/search.png rename to .themes/classic/source/images/search.png diff --git a/.themes/classic/source/index.html b/.themes/classic/source/index.html new file mode 100644 index 0000000..a114e5a --- /dev/null +++ b/.themes/classic/source/index.html @@ -0,0 +1,29 @@ +--- +layout: default +--- + +
+ {% assign index = true %} + {% for post in paginator.posts %} + {% assign content = post.content %} +
+ {% include article.html %} +
+ {% endfor %} + +
+ diff --git a/javascripts/github.js b/.themes/classic/source/javascripts/github.js similarity index 78% rename from javascripts/github.js rename to .themes/classic/source/javascripts/github.js index 27a5a23..fc2bb12 100644 --- a/javascripts/github.js +++ b/.themes/classic/source/javascripts/github.js @@ -13,8 +13,8 @@ var github = (function(){ return { showRepos: function(options){ $.ajax({ - url: "https://api.github.com/users/"+options.user+"/repos?callback=?" - , type: 'jsonp' + url: "https://api.github.com/users/"+options.user+"/repos?sort=pushed&callback=?" + , dataType: 'jsonp' , error: function (err) { $(options.target + ' li.loading').addClass('error').text("Error loading feed"); } , success: function(data) { var repos = []; @@ -23,14 +23,6 @@ var github = (function(){ if (options.skip_forks && data.data[i].fork) { continue; } repos.push(data.data[i]); } - repos.sort(function(a, b) { - var aDate = new Date(a.pushed_at).valueOf(), - bDate = new Date(b.pushed_at).valueOf(); - - if (aDate === bDate) { return 0; } - return aDate > bDate ? -1 : 1; - }); - if (options.count) { repos.splice(options.count); } render(options.target, repos); } diff --git a/javascripts/libs/jXHR.js b/.themes/classic/source/javascripts/libs/jXHR.js similarity index 100% rename from javascripts/libs/jXHR.js rename to .themes/classic/source/javascripts/libs/jXHR.js diff --git a/javascripts/libs/jquery.min.js b/.themes/classic/source/javascripts/libs/jquery.min.js similarity index 100% rename from javascripts/libs/jquery.min.js rename to .themes/classic/source/javascripts/libs/jquery.min.js diff --git a/javascripts/libs/swfobject-dynamic.js b/.themes/classic/source/javascripts/libs/swfobject-dynamic.js similarity index 100% rename from javascripts/libs/swfobject-dynamic.js rename to .themes/classic/source/javascripts/libs/swfobject-dynamic.js diff --git a/.themes/classic/source/javascripts/modernizr-2.0.js b/.themes/classic/source/javascripts/modernizr-2.0.js new file mode 100644 index 0000000..9dca7b0 --- /dev/null +++ b/.themes/classic/source/javascripts/modernizr-2.0.js @@ -0,0 +1,5 @@ +/* Modernizr 2.6.2 (Custom Build) | MIT & BSD + * Build: http://modernizr.com/download/#-video-mq-cssclasses-teststyles-testprop-testallprops-prefixes-domprefixes-load + */ +;window.Modernizr=function(a,b,c){function A(a){j.cssText=a}function B(a,b){return A(m.join(a+";")+(b||""))}function C(a,b){return typeof a===b}function D(a,b){return!!~(""+a).indexOf(b)}function E(a,b){for(var d in a){var e=a[d];if(!D(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function F(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:C(f,"function")?f.bind(d||b):f}return!1}function G(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+o.join(d+" ")+d).split(" ");return C(b,"string")||C(b,"undefined")?E(e,b):(e=(a+" "+p.join(d+" ")+d).split(" "),F(e,b,c))}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n="Webkit Moz O ms",o=n.split(" "),p=n.toLowerCase().split(" "),q={},r={},s={},t=[],u=t.slice,v,w=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["­",'"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},x=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return w("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},y={}.hasOwnProperty,z;!C(y,"undefined")&&!C(y.call,"undefined")?z=function(a,b){return y.call(a,b)}:z=function(a,b){return b in a&&C(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e}),q.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c};for(var H in q)z(q,H)&&(v=H.toLowerCase(),e[v]=q[H](),t.push((e[v]?"":"no-")+v));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)z(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},A(""),i=k=null,e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.mq=x,e.testProp=function(a){return E([a])},e.testAllProps=G,e.testStyles=w,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+t.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f').next().append(''); - mobileNav.children('select').append(''); - $('ul[role=main-navigation]').addClass('main-navigation'); - $('ul.main-navigation a').each(function(link) { - mobileNav.children('select').append(''); - }); - $('ul.subscription a').each(function(link) { - mobileNav.children('select').append(''); - }); - mobileNav.children('select').bind('change', function(event) { + var mainNav = $('ul.main-navigation, ul[role=main-navigation]').before('
') + var mobileNav = $('fieldset.mobile-nav').append(' - - - - - - - - - -
-

Catamorphisms in 15 Minutes!

-

Let’s say you have a list of numbers. What can you do with it? Find its length, map a function over it, add them up, etc. Wouldn’t it be great if there were one function to rule them all, a function that generalizes what you can do with a list? In many languages, there is a higher-order function called fold that encapsulates a pattern of structural recursion over recursive structures: to specify a function on a list, you specify two exhaustive cases: what to do to an empty list, and what to do with any other list. A generalization of this generalization is the notion of a catamorphism, which is what I’m writing about today.

- - - -

Since catamorphisms are so general, this is really going to be a whirlwind tour through category theory, winding up talking about catas near the end. This is all non-rigorous and hand-wavey, but I hope you can at least hear these concepts later and they make a little bit of sense.

- -

There are many resources about fold (see Graham Hutton’s tutorial) and catamorphisms (see, for example, Edward Kmett’s knol). I wanted to slightly expand on some elementary details from these works. So let’s jump in!

- -

The Shortest Category Theory Tutorial in the World

- -

A category consists of:

- -
    -
  • a collection of objects, such as , and
  • -
  • arrows or morphisms, such as , between objects. You can read this as “ is an arrow from to ”.
  • -
- -

In addition to this structure, objects and arrows must satisfy the following properties:

- -
    -
  • If and , then there must be an arrow from to . This arrow is the composition of and and is written .
  • -
  • For each object , there is an identity arrow from to itself: .
  • -
- - - -

Both of these requirements are shown in the diagram. Each object has an (unlabeled) identity arrow. Notice how I can get from to by either going from to and then from to , or going “directly” there with the composed arrow. This “doesn’t matter which path you take” property of the diagram is called commutation, and so this diagram commutes. Diagrams in category theory are paper tools: they summarize whole paragraphs of proofs in one, maybe complicated figure.

- -

Composition must be associative, which means . So we can ignore the parentheses! The identity arrows take an arrow back to itself: and if $f: A \rightarrow B$.

- -

There. Now you know what category theory is. It’s useful because lots of mathematical structures are categories, like sets, groups, algebras, and so on.

- -

Pro tip: whenever I think of a category, I pretend that I’m really talking about sets to get some intuiton. In the category of sets, sets themselves are the objects and regular old functions / mappings are the arrows. Beware though: in general, arrows don’t always behave like functions!

- -

-There are a ton of great books, both free and otherwise, and even videos about category theory, so I’ll direct you to them for a deeper look since I’ve only covered about 0% of the subject. As Bird and de Moor say in Algebra of Programming, “One does not so much learn category theory as absorb it over a period of time.” So go easy on yourself. Let’s move on to ever-higher levels of abstraction and talk about… -

- -

Functors

- -

The name “functor” has always sounded like a villain from a sci-fi novel, which probably explains why I find them so interesting.

- -

I already mentioned what a category was. What would happen if I could make some kind of mapping between categories? Whoa. I know, amazing, right? This mapping is called a functor and it transforms a diagram into another diagram.

- -

The neat thing is, even if the objects in the mapped category are completely different from the original category, the structure of the functor’d category is the same as the original’s. I’ve just described a homomorphism, which is a mapping that preserves structure (it means “same form” in Greek). Since a functor acts on a whole category, you have to describe its action on both objects and arrows. Let be a functor from category to category . Then:

- -
    -
  • gets mapped to
  • -
  • get mapped to
  • -
- -

Everyone uses the same symbol to refer to both the arrow mapping and the object mapping. The functor laws analogous to the requirements of a category are:

- -
    -
  • for all objects $A$
  • -
  • for all arrows $f$ and $g$.
  • -
- -

Or, in diagrams (adapted from the XyJax page):

- - - -

Gorgeous. Let’s say what a functor is one more time: a homomorphism between categories.

- -

If you program in Haskell, you’ve probably already heard of its Functor typeclass, which has the following definition:

- -
class Functor f where
-	fmap :: (a -> b) -> f a -> f b
-
- -

This means that when you create a functor in Haskell, you need to define what the fmap function does, and from its signature, I see that it takes functions of type $A \rightarrow B$ to $\textbf{F}A \rightarrow \textbf{F}B$, similar to what we discussed above. See the discussion in Learn You A Haskell for more functor fun.

- -

From these dizzying heights of thinking, let’s think about special objects or functors that will be useful in the goal of generally processing data types like lists as mentioned earlier.

- -

Initial Objects

- -

In some categories, you can imagine that there are some objects that are in some sense “unspecified” or “empty”. These objects are special, like {}, the empty set (the set with the least amount of useful information), or the empty list. These objects share the trait of being unique: there’s only one empty set, only one number such that when you add it to any other number, you get that number back (zero), and so on. In category theory, we can think of this trait more generally, where it’s called initiality. The term “initial” makes sense to me since I think of the category of sets “starting off” with the empty set, for example.

- -

An object is initial if, for any object in the category, there is only one mapping from it to that object. I’ll call the initial object in a category $\textbf{0}$. So in other words, for an object $A$, there is only one arrow of the form $\textbf{0} \rightarrow A$.

- -

Can there be more than one initial object? Let’s pretend the answer is yes and see what that would imply. Let $\textbf{0}$ and $\textbf{0’}$ be initial objects. Then there’s an arrow and :

- - - -

From the diagram, it’s clear that starting from $f$ and coming back through $g$ is a function from $\textbf{0}$ to itself: $g \circ f: \textbf{0} \rightarrow \textbf{0}$. But here’s the trick: since $\textbf{0}$ is initial, there’s only one object from it to any object–including $\textbf{0}$!

- -

So we have that (this is the identity arrow for $\textbf{0}$). I could have started with $\textbf{0’}$ instead, and we would have gotten . So $\textbf{0}$ and $\textbf{0’}$ are basically the same object; the technical term is that they are isomorphic, which means “equal form”. Isomorphisms are sort of like the category-theoretic version of equality.

- -

How does all this help us with our goal of understanding structural recursion and recursive structures? Well, the uniqueness of initial objects (up to isomorphism) is similar to a uniqueness proof: if we come up with some function or procedure on a list, we want to know that it’s the solution (or at least how many there are, even if it’s zero). Functors give us the notion taking a data type to another data type. Let’s combine initial objects and functors and get: F-algebras and catamorphisms!

- -

Mighty Catamorphisms

- -

I very much hope the title of this section made you think of a certain 90’s kid’s show. You’re welcome.

- -

Here’s an example of a recursive structure: non-negative integers! Yes, ever since you learned to count, you’ve been thinking recursively. Good for you! So a non-negative integer is one of two things: zero, or 1 plus another non-negative integer. Or in Haskell, you could write:

- -
data NonNeg = Zero | OnePlus NonNeg
-
- -

So what does “2” look like here? Did you guess OnePlus(OnePlus Zero)? Great! We can reason this through since 2 is either 0 (nope) or 1 plus another number. Well what’s 1? Either 0 (nope again) or 1 plus another number 0. Well what’s 0? Either 0–yep! And now we’re done.

- -

Now, I wouldn’t want to represent the number 839921 like this, but it shows that we can define a useful structure recursively–and sometimes, this may be the best way to do so.

- -

Say, now that I think about it, NonNeg is an example of a category! What we’re interested in is some kind of transformation from this category to itself, and by “transformation” I mean “functor”. A functor that maps a category into itself is called an endofunctor (endo- meaning “inside” in Greek).

- -

But we want a little more than that. In the example involving “2” above, we wanted to transform it into either 0 or 1 plus another number. Or if you’re doing addition, we want an mapping that takes pairs of numbers into another number. So we need arrows with signatures like: , where $A$ here could mean a type, like natural numbers with addition, or NonNeg.

- -

Congrats! You’ve just learned what an F-algebra is: an arrow with type .

- -

By now, you know what I’m going to wonder: do F-algebras themselves form a category, where the objects are F-algebras? Well, we need to determine what the arrows are (and don’t forget that an F-algebra is itself an arrow…).

- -

Let be a category, and let be an endofunctor. If $A$ and $B$ are two objects in , then we have two F-algebras, and . Let’s define a mapping between $A$ and $B$: $z: A \rightarrow B$. So far, we can summarize all these crazy arrows in a diagram:

- - - -

I added the action of $\textbf{F}$ on $z$ already. Now, if this diagram is to commute, both paths from to have to be equal; in other words:

- - - -

If $z$ satisfies this equation, it’s called an F-homomorphism since it preserves the structure of the F-algebra.

- -

Now, since the identity arrow is a homomorphism, and composing two homomorphisms gives another homomorphism, that means that F-algebras themselves form a category. Here’s where it gets interesting. For a general class of functor, this category of F-algebras has an initial object–an initial algebra. I’ll choose for the initial algebra: .

- -

So now we have the same diagram as above, except I’ll label with the initial algebra:

- - - -

And there it is! The catamorphism, which I’ve labeled as $g^\star$, is the unique homomorphism from the initial algebra to any other F-algebra. In the bananas, lenses, and barbed wire paper, it’s defined with banana brackets, which I don’t know how to generate. Oh well! Its definition is:

- - - -

That’s…very abstract. How is this useful? The utility here comes from thinking of the initial algebra as a data type. Let’s pick one: lists in Haskell. A list of type $A$ is either the empty list or a value of type $A$ attached (cons‘ed, if you will) to the back of a list. In other words:

- -
data List a = Nil | Cons a (List a)
-
- -

This will be our . More explicitly, we can write the endofunctor as . Here, $1$ represents the empty list, and $A \times X$ represents the Cartesian product of a type $A$ connected to a type $X$, or the non-empty list, and $+$ is a disjoint sum, sort of like the | in Haskell. Equations like are awesome because they imply that F-algebras encode a notion of self-similarity, like a fractal. In fact, there is a correspondence between these data types and least fixed points; for more on this, check out the great article at FP Complete.

- -

Fun fact: The term catamorphism was coined by Lambert Meertens. The “cata” in “catamorphism”, according to Grant Malcolm, supposedly means “according to” or “following”, so a catamorphism “follows” the structure of the data type. On the other hand, it could also mean “downward”, like “catabolic” or “catastrophic”, and a catamorphism does “plunge into” a data structure. Since it could mean either, but the former is the primary meaning, let’s go with that!

- -

So now we can define a mapping from the initial algebra and, say, the natural numbers. We can define the length of a list, a function from lists to integers. Because a catamorphism is a homomorphism, it will preserve the structure of the list type; our function needs to figure out what to map Nil to (in length’s case, 0), and what to map Cons to (in this case, addition). Because a catamorphism is a mapping from an initial algebra, we thus know that it’s the unique solution.

- -

Thus, if we have the following two equations to solve:

- -
z Nil = c
-z (Cons a as) = f a (z as)
-
- -

where we transform Nil to c and Cons to f (both binary functions), then we immediately know that z is a catamophism, and the unique solution to those equations is given by the catamorphism diagram above. In fact, functional programmers are already familiar with this function: it’s just good ol’ foldr in Haskell:

- -
z = foldr f c
-
- -

Fusion Techniques!

- -

So we’ve discussed catamorphisms on lists, and the definition generalizes to any category where we can define an initial algebra: the natural numbers, binary trees, multiway trees; the sky is the limit. The catamorphism will look different with other data types, but its definition is the same. This is why category theory is useful–do a lot of abstract work once and watch it pay off again and again.

- -

Probably the most useful thing about catamorphisms, though, is fusion, when you can combine a mapping with a catamorphism and get another catamorphism. This could, for example, make a computation more efficient, or make intermediate transformations in a compiler more efficient.

- -

You know what? By now, you’re a pro at reading diagrams, so let’s look at the one that represents fusion:

- - - -

The bottom square commutes and is the same one as before. The upper square defines another F-homomorphism. If both squares commute, the whole rectangle commutes. The fusion law is immediately read off from the diagram:

- - - -

This fuses two separate operations into one–it folds two folds into one!

- -

Further Reading

- -

Okay, so that was probably more than 15 minutes of reading. But now you can dive deeper into any of the sources listed throughout this post. A lot of the literature is in the realm of theses, drafts, and papers with the notable exception of Algebra of Programming, which I drew heavily upon in writing this. I highly recommend that last one!

- -
- - -
-
- - - - - - - - - - - -
- - - - - - - Comments - -
- - - - - -
-

Comments

-
-
-
-
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/about/index.html b/about/index.html deleted file mode 100644 index 691d0cc..0000000 --- a/about/index.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - - about | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

I’m Chris Jones. I attended MIT for doctoral studies in experimental physics and completed my Ph.D. in 2012. During my studies, I became interested in big data problems (filling up petabytes per day was a problem!) and functional programming, programming languages, and compilers. I especially love where all four of these topics coincide.

- -

Like many programming nerds, I love $\lambda$ as a symbol of abstraction. But here, it refers not only to the lambda calculus, but also to the Lambda Architecture in Big Data systems.

- -

I left academia and now do software development and data science for a living.

- -

I love teaching and education, and one of the points of this site is to record what I learn as I learn it.

- -

Follow me on Twitter.

- -

Questions I’ve answered on Quora.

- -
-
- - -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - diff --git a/archives/index.html b/archives/index.html deleted file mode 100644 index 180ccfd..0000000 --- a/archives/index.html +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - - - Blog Archives | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -

2014

- - - - - - - -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/atom.xml b/atom.xml deleted file mode 100644 index 8bda04a..0000000 --- a/atom.xml +++ /dev/null @@ -1,382 +0,0 @@ - - - - <![CDATA[Stack Unwinding]]> - - - 2014-02-02T18:42:05-05:00 - http://chrislambda.github.io/ - - - - - Octopress - - - - <![CDATA[Catamorphisms in 15 Minutes!]]> - - 2014-01-30T20:53:57-05:00 - http://chrislambda.github.io/blog/2014/01/30/catamorphisms-in-15-minutes - Let’s say you have a list of numbers. What can you do with it? Find its length, map a function over it, add them up, etc. Wouldn’t it be great if there were one function to rule them all, a function that generalizes what you can do with a list? In many languages, there is a higher-order function called fold that encapsulates a pattern of structural recursion over recursive structures: to specify a function on a list, you specify two exhaustive cases: what to do to an empty list, and what to do with any other list. A generalization of this generalization is the notion of a catamorphism, which is what I’m writing about today.

- - - -

Since catamorphisms are so general, this is really going to be a whirlwind tour through category theory, winding up talking about catas near the end. This is all non-rigorous and hand-wavey, but I hope you can at least hear these concepts later and they make a little bit of sense.

- -

There are many resources about fold (see Graham Hutton’s tutorial) and catamorphisms (see, for example, Edward Kmett’s knol). I wanted to slightly expand on some elementary details from these works. So let’s jump in!

- -

The Shortest Category Theory Tutorial in the World

- -

A category consists of:

- -
    -
  • a collection of objects, such as , and
  • -
  • arrows or morphisms, such as , between objects. You can read this as “ is an arrow from to ”.
  • -
- -

In addition to this structure, objects and arrows must satisfy the following properties:

- -
    -
  • If and , then there must be an arrow from to . This arrow is the composition of and and is written .
  • -
  • For each object , there is an identity arrow from to itself: .
  • -
- - - -

Both of these requirements are shown in the diagram. Each object has an (unlabeled) identity arrow. Notice how I can get from to by either going from to and then from to , or going “directly” there with the composed arrow. This “doesn’t matter which path you take” property of the diagram is called commutation, and so this diagram commutes. Diagrams in category theory are paper tools: they summarize whole paragraphs of proofs in one, maybe complicated figure.

- -

Composition must be associative, which means . So we can ignore the parentheses! The identity arrows take an arrow back to itself: and if $f: A \rightarrow B$.

- -

There. Now you know what category theory is. It’s useful because lots of mathematical structures are categories, like sets, groups, algebras, and so on.

- -

Pro tip: whenever I think of a category, I pretend that I’m really talking about sets to get some intuiton. In the category of sets, set elements are the objects and regular old functions / mappings are the arrows. Beware though: in general, arrows don’t always behave like functions!

- -

-There are a ton of great books, both free and otherwise, and even videos about category theory, so I’ll direct you to them for a deeper look since I’ve only covered about 0% of the subject. As Bird and de Moor say in Algebra of Programming, “One does not so much learn category theory as absorb it over a period of time.” So go easy on yourself. Let’s move on to ever-higher levels of abstraction and talk about… -

- -

Functors

- -

The name “functor” has always sounded like a villain from a sci-fi novel, which probably explains why I find them so interesting.

- -

I already mentioned what a category was. What would happen if I could make some kind of mapping between categories? Whoa. I know, amazing, right? This mapping is called a functor and it transforms a diagram into another diagram.

- -

The neat thing is, even if the objects in the mapped category are completely different from the original category, the structure of the functor’d category is the same as the original’s. I’ve just described a homomorphism, which is a mapping that preserves structure (it means “same form” in Greek). Since a functor acts on a whole category, you have to describe its action on both objects and arrows. Let be a functor from category to category . Then:

- -
    -
  • gets mapped to
  • -
  • get mapped to
  • -
- -

Everyone uses the same symbol to refer to both the arrow mapping and the object mapping. The functor laws analogous to the requirements of a category are:

- -
    -
  • for all objects $A$
  • -
  • for all arrows $f$ and $g$.
  • -
- -

Or, in diagrams (adapted from the XyJax page):

- - - -

Gorgeous. Let’s say what a functor is one more time: a homomorphism between categories.

- -

If you program in Haskell, you’ve probably already heard of its Functor typeclass, which has the following definition:

- -
class Functor f where
-	fmap :: (a -> b) -> f a -> f b
-
- -

This means that when you create a functor in Haskell, you need to define what the fmap function does, and from its signature, I see that it takes functions of type $A \rightarrow B$ to $\textbf{F}A \rightarrow \textbf{F}B$, similar to what we discussed above. See the discussion in Learn You A Haskell for more functor fun.

- -

From these dizzying heights of thinking, let’s think about special objects or functors that will be useful in the goal of generally processing data types like lists as mentioned earlier.

- -

Initial Objects

- -

In some categories, you can imagine that there are some objects that are in some sense “unspecified” or “empty”. These objects are special, like {}, the empty set (the set with the least amount of useful information), or the empty list. These objects share the trait of being unique: there’s only one empty set, only one number such that when you add it to any other number, you get that number back (zero), and so on. In category theory, we can think of this trait more generally, where it’s called initiality. The term “initial” makes sense to me since I think of the category of sets “starting off” with the empty set, for example.

- -

An object is initial if, for any object in the category, there is only one mapping from it to that object. I’ll call the initial object in a category $\textbf{0}$. So in other words, for an object $A$, there is only one arrow of the form $\textbf{0} \rightarrow A$.

- -

Can there be more than one initial object? Let’s pretend the answer is yes and see what that would imply. Let $\textbf{0}$ and $\textbf{0’}$ be initial objects. Then there’s an arrow and :

- - - -

From the diagram, it’s clear that starting from $f$ and coming back through $g$ is a function from $\textbf{0}$ to itself: $g \circ f: \textbf{0} \rightarrow \textbf{0}$. But here’s the trick: since $\textbf{0}$ is initial, there’s only one object from it to any object–including $\textbf{0}$!

- -

So we have that (this is the identity arrow for $\textbf{0}$). I could have started with $\textbf{0’}$ instead, and we would have gotten . So $\textbf{0}$ and $\textbf{0’}$ are basically the same object; the technical term is that they are isomorphic, which means “equal form”. Isomorphisms are sort of like the category-theoretic version of equality.

- -

How does all this help us with our goal of understanding structural recursion and recursive structures? Well, the uniqueness of initial objects (up to isomorphism) is similar to a uniqueness proof: if we come up with some function or procedure on a list, we want to know that it’s the solution (or at least how many there are, even if it’s zero). Functors give us the notion taking a data type to another data type. Let’s combine initial objects and functors and get: F-algebras and catamorphisms!

- -

Mighty Catamorphisms

- -

I very much hope the title of this section made you think of a certain 90’s kid’s show. You’re welcome.

- -

Here’s an example of a recursive structure: non-negative integers! Yes, ever since you learned to count, you’ve been thinking recursively. Good for you! So a non-negative integer is one of two things: zero, or 1 plus another non-negative integer. Or in Haskell, you could write:

- -
data NonNeg = Zero | OnePlus NonNeg
-
- -

So what does “2” look like here? Did you guess OnePlus(OnePlus Zero)? Great! We can reason this through since 2 is either 0 (nope) or 1 plus another number. Well what’s 1? Either 0 (nope again) or 1 plus another number 0. Well what’s 0? Either 0–yep! And now we’re done.

- -

Now, I wouldn’t want to represent the number 839921 like this, but it shows that we can define a useful structure recursively–and sometimes, this may be the best way to do so.

- -

Say, now that I think about it, NonNeg is an example of a category! What we’re interested in is some kind of transformation from this category to itself, and by “transformation” I mean “functor”. A functor that maps a category into itself is called an endofunctor (endo- meaning “inside” in Greek).

- -

But we want a little more than that. In the example involving “2” above, we wanted to transform it into either 0 or 1 plus another number. Or if you’re doing addition, we want an mapping that takes pairs of numbers into another number. So we need arrows with signatures like: , where $A$ here could mean a type, like natural numbers with addition, or NonNeg.

- -

Congrats! You’ve just learned what an F-algebra is: an arrow with type .

- -

By now, you know what I’m going to wonder: do F-algebras themselves form a category, where the objects are F-algebras? Well, we need to determine what the arrows are (and don’t forget that an F-algebra is itself an arrow…).

- -

Let be a category, and let be an endofunctor. If $A$ and $B$ are two objects in , then we have two F-algebras, and . Let’s define a mapping between $A$ and $B$: $z: A \rightarrow B$. So far, we can summarize all these crazy arrows in a diagram:

- - - -

I added the action of $\textbf{F}$ on $z$ already. Now, if this diagram is to commute, both paths from to have to be equal; in other words:

- - - -

If $z$ satisfies this equation, it’s called an F-homomorphism since it preserves the structure of the F-algebra.

- -

Now, since the identity arrow is a homomorphism, and composing two homomorphisms gives another homomorphism, that means that F-algebras themselves form a category. Here’s where it gets interesting. For a general class of functor, this category of F-algebras has an initial object–an initial algebra. I’ll choose for the initial algebra: .

- -

So now we have the same diagram as above, except I’ll label with the initial algebra:

- - - -

And there it is! The catamorphism, which I’ve labeled as $g^\star$, is the unique homomorphism from the initial algebra to any other F-algebra. In the bananas, lenses, and barbed wire paper, it’s defined with banana brackets, which I don’t know how to generate. Oh well! Its definition is:

- - - -

That’s…very abstract. How is this useful? The utility here comes from thinking of the initial algebra as a data type. Let’s pick one: lists in Haskell. A list of type $A$ is either the empty list or a value of type $A$ attached (cons‘ed, if you will) to the back of a list. In other words:

- -
data List a = Nil | Cons a (List a)
-
- -

This will be our . More explicitly, we can write the endofunctor as . Here, $1$ represents the empty list, and $A \times X$ represents the Cartesian product of a type $A$ connected to a type $X$, or the non-empty list, and $+$ is a disjoint sum, sort of like the | in Haskell. Equations like are awesome because they imply that F-algebras encode a notion of self-similarity, like a fractal. In fact, there is a correspondence between these data types and least fixed points; for more on this, check out the great article at FP Complete.

- -

Fun fact: The term catamorphism was coined by Lambert Meertens. The “cata” in “catamorphism”, according to Grant Malcolm, supposedly means “according to” or “following”, so a catamorphism “follows” the structure of the data type. On the other hand, it could also mean “downward”, like “catabolic” or “catastrophic”, and a catamorphism does “plunge into” a data structure. Since it could mean either, but the former is the primary meaning, let’s go with that!

- -

So now we can define a mapping from the initial algebra and, say, the natural numbers. We can define the length of a list, a function from lists to integers. Because a catamorphism is a homomorphism, it will preserve the structure of the list type; our function needs to figure out what to map Nil to (in length’s case, 0), and what to map Cons to (in this case, addition). Because a catamorphism is a mapping from an initial algebra, we thus know that it’s the unique solution.

- -

Thus, if we have the following two equations to solve:

- -
z Nil = c
-z (Cons a as) = f a (z as)
-
- -

where we transform Nil to c and Cons to f (both binary functions), then we immediately know that z is a catamophism, and the unique solution to those equations is given by the catamorphism diagram above. In fact, functional programmers are already familiar with this function: it’s just good ol’ foldr in Haskell:

- -
z = foldr f c
-
- -

Fusion Techniques!

- -

So we’ve discussed catamorphisms on lists, and the definition generalizes to any category where we can define an initial algebra: the natural numbers, binary trees, multiway trees; the sky is the limit. The catamorphism will look different with other data types, but its definition is the same. This is why category theory is useful–do a lot of abstract work once and watch it pay off again and again.

- -

Probably the most useful thing about catamorphisms, though, is fusion, when you can combine a mapping with a catamorphism and get another catamorphism. This could, for example, make a computation more efficient, or make intermediate transformations in a compiler more efficient.

- -

You know what? By now, you’re a pro at reading diagrams, so let’s look at the one that represents fusion:

- - - -

The bottom square commutes and is the same one as before. The upper square defines another F-homomorphism. If both squares commute, the whole rectangle commutes. The fusion law is immediately read off from the diagram:

- - - -

This fuses two separate operations into one–it folds two folds into one!

- -

Further Reading

- -

Okay, so that was probably more than 15 minutes of reading. But now you can dive deeper into any of the sources listed throughout this post. A lot of the literature is in the realm of theses, drafts, and papers with the notable exception of Algebra of Programming, which I drew heavily upon in writing this. I highly recommend that last one!

- -]]>
-
- - - <![CDATA[From Coding to Calculating]]> - - 2014-01-20T17:18:10-05:00 - http://chrislambda.github.io/blog/2014/01/20/from-coding-to-calculating - Like many aspiring software developers and computer scientists, I wanted to improve my art by going through Structure and Interpretation of Computer Programs. I fully expected to emerge from it all with Sherlock Holmes-esque abilities in programming. Yeah, that was about 7 years ago. Still, every so often, I’ll do some of the exercises and search online to see how my answers compared to others’.

- - - -

Because I didn’t spend a ton of time on these problems, I usually only implemented a naive solution and could only marvel at others’ much cooler, more clever, more efficient solutions. But I could never be sure if they were equivalent. Or could I?

- -

Here’s the example that inspired me to write this post, from SICP:

- -
-

Exercise 2.28. Write a procedure fringe that takes as argument a tree (represented as a list) and returns a list whose elements are all the leaves of the tree arranged in left-to-right order. For example,

-
- -
(define x (list (list 1 2) (list 3 4)))
-
-(fringe x)
-;; equals (1 2 3 4)
-
-(fringe (list x x))
-;; equals (1 2 3 4 1 2 3 4)
-
- -

If you’re not a Scheme user, this function takes something that looks like ((1 2) (3 4)) and gives something that looks like (1 2 3 4). It “gets the numbers out of the parentheses”. The nifty thing about this function is that it will do this for arbitrarily-nested trees, not just one level deep.

- -

This function also shows up in Paul Graham’s On Lisp, where it’s called flatten. In Let Over Lambda, Doug Hoyte points out that since Lisp code is represented by Lisp trees, flatten performs code-walking.

- -

How do you solve a problem like this? One way is to reason about the kinds of values your tree/list can be, and what your function should do with the tree/list when it assumes those values. In Scheme, a list can represent a binary tree. If tr is a tree, then (car tr) gives you the left branch and (cdr tr) gives you the right branch. So thinking recursively, let’s say someone’s already flattened the left and right branch. What now? Well, just append the flattened-left branch to the flattened-right branch and you’re done.

- -

I regard trees as being empty, being a leaf, or being a branch with two leaves. We covered the latter case just above, so let’s handle the other two:

- -
    -
  • -

    Empty, represented by the empty list (). What does a flattened empty list look like? That’s right: just the empty list, so return that.

    -
  • -
  • -

    A leaf. We want the function to return a list though, so return a list containing this value (i.e., wrap it in parentheses).

    -
  • -
- -

That’s all three cases! Here’s a naive solution in Common Lisp:

- -
(defun flatten (tree)
-	(cond ((null tree) nil)
-	      ((atom tree) (cons tree nil))
-	      (t (append (flatten (car tree))
-		         (flatten (cdr tree))))))
-
- -

Lisp code, so beautiful! Here, null tests for the empty list and atom tests to see if the tree is a leaf.

- -

This is all well and good, and it satisfies the test cases from SICP. But if you search for other answers to this question, or even look at the definition in On Lisp, there’s one that looks a bit different:

- -
(defun flatten (tree)
-   (labels ((rec (x acc)
-      (cond ((null x) acc)
-                ((atom x) (cons x acc))
-                (t (rec (car x) (rec (cdr x) acc))))))
-    (rec tree nil)))
-
- -

Here, labels introduces a local helper function in Common Lisp called rec that takes an accumulator. Except for this, it appears to be of the same “form” as the naive solution. It’s not immediately obvious to me how this second solution is equivalent to the first one. Even though I understood the logic of the second solution, I don’t think I would have come up with it on my own, even though I’m familiar with the accumulator technique.

- -

I wanted to not have to be clever. Even more, I wanted to be able to prove the equivalence of any other version of this function. What I wanted was to derive these equivalences in the same way I proved theorems in geometry or performed manipulations in algebra.

- -

Enter Haskell.

- -

I had been reading about Haskell here and there, and I appreciated that you could reason about programs, even calculate them. This is the method described in the book Algebra of Programming, which uses the Gofer language, an ancestor of Haskell. So I’ll recast this in Haskell because its syntax is ultra-clean. First, let’s roll out our own tree data type:

- -
data Tree a = Empty | Leaf a | Branch (Tree a) (Tree a)
-
- -

A tree can be either empty, a leaf holding a value of type a, or a branch of two trees, just like we said above. The flatten function translates into Haskell easily:

- -
flatten :: Tree a -> [a]
-flatten Empty = []
-flatten (Leaf a) = [a]
-flatten (Branch l r) = flatten l ++ flatten r
-
- -

Here, [a] means a list with one value in it, and ++ means “append”. The declaration at the top tells us this function takes a Tree holding values of type a and returns a list. Here’s an example of the function in action:

- -
flatten (Branch (Branch (Leaf 2) (Branch (Branch (Leaf 11) (Empty)) (Leaf 7))) (Leaf 4))
-[2,11,7,4]
-
- -

To make this look more like the second solution, let’s use the accumulator technique, the benefits of which I’ll go over in another post. So we define a helper function, flat_helper, that takes an accumulator as its second value:

- -
flat_helper :: Tree a -> [a] -> [a]
-flat_helper tree list = flatten tree ++ list
-
- -

We flatten tree, and then push the result onto list. I chose this form to “accumulate” the result into list. We can achieve our final result as follows:

- -
flat_helper tree [] = flatten tree ++ [] = flatten tree
-
- -

What does flat_helper do to the tree? The great thing about this is that we can just mechanically derive what flat_helper does from its definition:

- -
flat_helper Empty lst =
-  { definition of flat_helper }
-flatten Empty ++ lst =
-  { definition of flatten }
-[] ++ lst =
-lst
-
-flat_helper (Leaf a) lst =
-  { definition of flat_helper }
-flatten (Leaf a) ++ lst =
-  { definition of flatten }
-[a] ++ lst
-
-flat_helper (Branch l r) lst = 
-  { definition of flat_helper }
-flatten (Branch l r) ++ lst =
-  { definition of flatten }
-(flatten l ++ flatten r) ++ lst =
-  { associativity of append }
-flatten l ++ (flatten r ++ lst) =
-  { definition of flat_helper }
-flatten l ++ flat_helper r lst = 
-  { definition of flat_helper }
-flat_helper l (flat_helper r lst)
-
- -

Et voila! This is the same code from the second solution written in Haskell. I derived one solution from the other in a way that’s almost mathematical. It turns out that it’s more efficient than the naive solution, and we don’t even need to use ++. I wish I could learn from programs like this all the time.

- -

I based the above derivation from a chapter in Introduction to Functional Programming in Haskell, which has several more derivations like the above (I used their bracket comment notation). They called it synthesizing the program, which is a good way to think about it.

- -

I started out using computers to do work I would find tedious or unfeasible to do on my own–iterating over millions of variables, or printing a report every hour. Now I can use them to help me think about computers. A lot of fun for one problem in SICP, I’d say!

- -]]>
-
- -
diff --git a/blog/2014/01/20/from-coding-to-calculating/index.html b/blog/2014/01/20/from-coding-to-calculating/index.html deleted file mode 100644 index 0576cd1..0000000 --- a/blog/2014/01/20/from-coding-to-calculating/index.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - - - From Coding to Calculating | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

From Coding to Calculating

-

Like many aspiring software developers and computer scientists, I wanted to improve my art by going through Structure and Interpretation of Computer Programs. I fully expected to emerge from it all with Sherlock Holmes-esque abilities in programming. Yeah, that was about 7 years ago. Still, every so often, I’ll do some of the exercises and search online to see how my answers compared to others’.

- - - -

Because I didn’t spend a ton of time on these problems, I usually only implemented a naive solution and could only marvel at others’ much cooler, more clever, more efficient solutions. But I could never be sure if they were equivalent. Or could I?

- -

Here’s the example that inspired me to write this post, from SICP:

- -
-

Exercise 2.28. Write a procedure fringe that takes as argument a tree (represented as a list) and returns a list whose elements are all the leaves of the tree arranged in left-to-right order. For example,

-
- -
(define x (list (list 1 2) (list 3 4)))
-
-(fringe x)
-;; equals (1 2 3 4)
-
-(fringe (list x x))
-;; equals (1 2 3 4 1 2 3 4)
-
- -

If you’re not a Scheme user, this function takes something that looks like ((1 2) (3 4)) and gives something that looks like (1 2 3 4). It “gets the numbers out of the parentheses”. The nifty thing about this function is that it will do this for arbitrarily-nested trees, not just one level deep.

- -

This function also shows up in Paul Graham’s On Lisp, where it’s called flatten. In Let Over Lambda, Doug Hoyte points out that since Lisp code is represented by Lisp trees, flatten performs code-walking.

- -

How do you solve a problem like this? One way is to reason about the kinds of values your tree/list can be, and what your function should do with the tree/list when it assumes those values. In Scheme, a list can represent a binary tree. If tr is a tree, then (car tr) gives you the left branch and (cdr tr) gives you the right branch. So thinking recursively, let’s say someone’s already flattened the left and right branch. What now? Well, just append the flattened-left branch to the flattened-right branch and you’re done.

- -

I regard trees as being empty, being a leaf, or being a branch with two leaves. We covered the latter case just above, so let’s handle the other two:

- -
    -
  • -

    Empty, represented by the empty list (). What does a flattened empty list look like? That’s right: just the empty list, so return that.

    -
  • -
  • -

    A leaf. We want the function to return a list though, so return a list containing this value (i.e., wrap it in parentheses).

    -
  • -
- -

That’s all three cases! Here’s a naive solution in Common Lisp:

- -
(defun flatten (tree)
-	(cond ((null tree) nil)
-	      ((atom tree) (cons tree nil))
-	      (t (append (flatten (car tree))
-		         (flatten (cdr tree))))))
-
- -

Lisp code, so beautiful! Here, null tests for the empty list and atom tests to see if the tree is a leaf.

- -

This is all well and good, and it satisfies the test cases from SICP. But if you search for other answers to this question, or even look at the definition in On Lisp, there’s one that looks a bit different:

- -
(defun flatten (tree)
-   (labels ((rec (x acc)
-      (cond ((null x) acc)
-                ((atom x) (cons x acc))
-                (t (rec (car x) (rec (cdr x) acc))))))
-    (rec tree nil)))
-
- -

Here, labels introduces a local helper function in Common Lisp called rec that takes an accumulator. Except for this, it appears to be of the same “form” as the naive solution. It’s not immediately obvious to me how this second solution is equivalent to the first one. Even though I understood the logic of the second solution, I don’t think I would have come up with it on my own, even though I’m familiar with the accumulator technique.

- -

I wanted to not have to be clever. Even more, I wanted to be able to prove the equivalence of any other version of this function. What I wanted was to derive these equivalences in the same way I proved theorems in geometry or performed manipulations in algebra.

- -

Enter Haskell.

- -

I had been reading about Haskell here and there, and I appreciated that you could reason about programs, even calculate them. This is the method described in the book Algebra of Programming, which uses the Gofer language, an ancestor of Haskell. So I’ll recast this in Haskell because its syntax is ultra-clean. First, let’s roll out our own tree data type:

- -
data Tree a = Empty | Leaf a | Branch (Tree a) (Tree a)
-
- -

A tree can be either empty, a leaf holding a value of type a, or a branch of two trees, just like we said above. The flatten function translates into Haskell easily:

- -
flatten :: Tree a -> [a]
-flatten Empty = []
-flatten (Leaf a) = [a]
-flatten (Branch l r) = flatten l ++ flatten r
-
- -

Here, [a] means a list with one value in it, and ++ means “append”. The declaration at the top tells us this function takes a Tree holding values of type a and returns a list. Here’s an example of the function in action:

- -
flatten (Branch (Branch (Leaf 2) (Branch (Branch (Leaf 11) (Empty)) (Leaf 7))) (Leaf 4))
-[2,11,7,4]
-
- -

To make this look more like the second solution, let’s use the accumulator technique, the benefits of which I’ll go over in another post. So we define a helper function, flat_helper, that takes an accumulator as its second value:

- -
flat_helper :: Tree a -> [a] -> [a]
-flat_helper tree list = flatten tree ++ list
-
- -

We flatten tree, and then push the result onto list. I chose this form to “accumulate” the result into list. We can achieve our final result as follows:

- -
flat_helper tree [] = flatten tree ++ [] = flatten tree
-
- -

What does flat_helper do to the tree? The great thing about this is that we can just mechanically derive what flat_helper does from its definition:

- -
flat_helper Empty lst =
-  { definition of flat_helper }
-flatten Empty ++ lst =
-  { definition of flatten }
-[] ++ lst =
-lst
-
-flat_helper (Leaf a) lst =
-  { definition of flat_helper }
-flatten (Leaf a) ++ lst =
-  { definition of flatten }
-[a] ++ lst
-
-flat_helper (Branch l r) lst = 
-  { definition of flat_helper }
-flatten (Branch l r) ++ lst =
-  { definition of flatten }
-(flatten l ++ flatten r) ++ lst =
-  { associativity of append }
-flatten l ++ (flatten r ++ lst) =
-  { definition of flat_helper }
-flatten l ++ flat_helper r lst = 
-  { definition of flat_helper }
-flat_helper l (flat_helper r lst)
-
- -

Et voila! This is the same code from the second solution written in Haskell. I derived one solution from the other in a way that’s almost mathematical. It turns out that it’s more efficient than the naive solution, and we don’t even need to use ++. I wish I could learn from programs like this all the time.

- -

I based the above derivation from a chapter in Introduction to Functional Programming in Haskell, which has several more derivations like the above (I used their bracket comment notation). They called it synthesizing the program, which is a good way to think about it.

- -

I started out using computers to do work I would find tedious or unfeasible to do on my own–iterating over millions of variables, or printing a report every hour. Now I can use them to help me think about computers. A lot of fun for one problem in SICP, I’d say!

- -
- - -
-
- - - - - - - - - - - -
- - - - - - - Comments - -
- - - - - -
-

Comments

-
-
-
-
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/blog/2014/01/30/catamorphisms-in-15-minutes/index.html b/blog/2014/01/30/catamorphisms-in-15-minutes/index.html deleted file mode 100644 index 72ada99..0000000 --- a/blog/2014/01/30/catamorphisms-in-15-minutes/index.html +++ /dev/null @@ -1,433 +0,0 @@ - - - - - - - - Catamorphisms in 15 Minutes! | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Catamorphisms in 15 Minutes!

-

Let’s say you have a list of numbers. What can you do with it? Find its length, map a function over it, add them up, etc. Wouldn’t it be great if there were one function to rule them all, a function that generalizes what you can do with a list? In many languages, there is a higher-order function called fold that encapsulates a pattern of structural recursion over recursive structures: to specify a function on a list, you specify two exhaustive cases: what to do to an empty list, and what to do with any other list. A generalization of this generalization is the notion of a catamorphism, which is what I’m writing about today.

- - - -

Since catamorphisms are so general, this is really going to be a whirlwind tour through category theory, winding up talking about catas near the end. This is all non-rigorous and hand-wavey, but I hope you can at least hear these concepts later and they make a little bit of sense.

- -

There are many resources about fold (see Graham Hutton’s tutorial) and catamorphisms (see, for example, Edward Kmett’s knol). I wanted to slightly expand on some elementary details from these works. So let’s jump in!

- -

The Shortest Category Theory Tutorial in the World

- -

A category consists of:

- -
    -
  • a collection of objects, such as , and
  • -
  • arrows or morphisms, such as , between objects. You can read this as “ is an arrow from to ”.
  • -
- -

In addition to this structure, objects and arrows must satisfy the following properties:

- -
    -
  • If and , then there must be an arrow from to . This arrow is the composition of and and is written .
  • -
  • For each object , there is an identity arrow from to itself: .
  • -
- - - -

Both of these requirements are shown in the diagram. Each object has an (unlabeled) identity arrow. Notice how I can get from to by either going from to and then from to , or going “directly” there with the composed arrow. This “doesn’t matter which path you take” property of the diagram is called commutation, and so this diagram commutes. Diagrams in category theory are paper tools: they summarize whole paragraphs of proofs in one, maybe complicated figure.

- -

Composition must be associative, which means . So we can ignore the parentheses! The identity arrows take an arrow back to itself: and if $f: A \rightarrow B$.

- -

There. Now you know what category theory is. It’s useful because lots of mathematical structures are categories, like sets, groups, algebras, and so on.

- -

Pro tip: whenever I think of a category, I pretend that I’m really talking about sets to get some intuiton. In the category of sets, sets themselves are the objects and regular old functions / mappings are the arrows. Beware though: in general, arrows don’t always behave like functions!

- -

-There are a ton of great books, both free and otherwise, and even videos about category theory, so I’ll direct you to them for a deeper look since I’ve only covered about 0% of the subject. As Bird and de Moor say in Algebra of Programming, “One does not so much learn category theory as absorb it over a period of time.” So go easy on yourself. Let’s move on to ever-higher levels of abstraction and talk about… -

- -

Functors

- -

The name “functor” has always sounded like a villain from a sci-fi novel, which probably explains why I find them so interesting.

- -

I already mentioned what a category was. What would happen if I could make some kind of mapping between categories? Whoa. I know, amazing, right? This mapping is called a functor and it transforms a diagram into another diagram.

- -

The neat thing is, even if the objects in the mapped category are completely different from the original category, the structure of the functor’d category is the same as the original’s. I’ve just described a homomorphism, which is a mapping that preserves structure (it means “same form” in Greek). Since a functor acts on a whole category, you have to describe its action on both objects and arrows. Let be a functor from category to category . Then:

- -
    -
  • gets mapped to
  • -
  • get mapped to
  • -
- -

Everyone uses the same symbol to refer to both the arrow mapping and the object mapping. The functor laws analogous to the requirements of a category are:

- -
    -
  • for all objects $A$
  • -
  • for all arrows $f$ and $g$.
  • -
- -

Or, in diagrams (adapted from the XyJax page):

- - - -

Gorgeous. Let’s say what a functor is one more time: a homomorphism between categories.

- -

If you program in Haskell, you’ve probably already heard of its Functor typeclass, which has the following definition:

- -
class Functor f where
-	fmap :: (a -> b) -> f a -> f b
-
- -

This means that when you create a functor in Haskell, you need to define what the fmap function does, and from its signature, I see that it takes functions of type $A \rightarrow B$ to $\textbf{F}A \rightarrow \textbf{F}B$, similar to what we discussed above. See the discussion in Learn You A Haskell for more functor fun.

- -

From these dizzying heights of thinking, let’s think about special objects or functors that will be useful in the goal of generally processing data types like lists as mentioned earlier.

- -

Initial Objects

- -

In some categories, you can imagine that there are some objects that are in some sense “unspecified” or “empty”. These objects are special, like {}, the empty set (the set with the least amount of useful information), or the empty list. These objects share the trait of being unique: there’s only one empty set, only one number such that when you add it to any other number, you get that number back (zero), and so on. In category theory, we can think of this trait more generally, where it’s called initiality. The term “initial” makes sense to me since I think of the category of sets “starting off” with the empty set, for example.

- -

An object is initial if, for any object in the category, there is only one mapping from it to that object. I’ll call the initial object in a category $\textbf{0}$. So in other words, for an object $A$, there is only one arrow of the form $\textbf{0} \rightarrow A$.

- -

Can there be more than one initial object? Let’s pretend the answer is yes and see what that would imply. Let $\textbf{0}$ and $\textbf{0’}$ be initial objects. Then there’s an arrow and :

- - - -

From the diagram, it’s clear that starting from $f$ and coming back through $g$ is a function from $\textbf{0}$ to itself: $g \circ f: \textbf{0} \rightarrow \textbf{0}$. But here’s the trick: since $\textbf{0}$ is initial, there’s only one mapping from it to any object–including $\textbf{0}$!

- -

So we have that (this is the identity arrow for $\textbf{0}$). I could have started with $\textbf{0’}$ instead, and we would have gotten . So $\textbf{0}$ and $\textbf{0’}$ are basically the same object; the technical term is that they are isomorphic, which means “equal form”. Isomorphisms are sort of like the category-theoretic version of equality.

- -

How does all this help us with our goal of understanding structural recursion and recursive structures? Well, the uniqueness of initial objects (up to isomorphism) is similar to a uniqueness proof: if we come up with some function or procedure on a list, we want to know that it’s the solution (or at least how many there are, even if it’s zero). Functors give us the notion taking a data type to another data type. Let’s combine initial objects and functors and get: F-algebras and catamorphisms!

- -

Mighty Catamorphisms

- -

I very much hope the title of this section made you think of a certain 90’s kid’s show. You’re welcome.

- -

Here’s an example of a recursive structure: non-negative integers! Yes, ever since you learned to count, you’ve been thinking recursively. Good for you! So a non-negative integer is one of two things: zero, or 1 plus another non-negative integer. Or in Haskell, you could write:

- -
data NonNeg = Zero | OnePlus NonNeg
-
- -

So what does “2” look like here? Did you guess OnePlus(OnePlus Zero)? Great! We can reason this through since 2 is either 0 (nope) or 1 plus another number. Well what’s 1? Either 0 (nope again) or 1 plus another number 0. Well what’s 0? Either 0–yep! And now we’re done.

- -

Now, I wouldn’t want to represent the number 839921 like this, but it shows that we can define a useful structure recursively–and sometimes, this may be the best way to do so.

- -

Say, now that I think about it, NonNeg is an example of a category! What we’re interested in is some kind of transformation from this category to itself, and by “transformation” I mean “functor”. A functor that maps a category into itself is called an endofunctor (endo- meaning “inside” in Greek).

- -

But we want a little more than that. In the example involving “2” above, we wanted to transform it into either 0 or 1 plus another number. Or if you’re doing addition, we want a mapping that takes pairs of numbers into another number. So we need arrows with signatures like: , where $A$ here could mean a type, like natural numbers with addition, or NonNeg.

- -

Congrats! You’ve just learned what an F-algebra is: an arrow with type .

- -

By now, you know what I’m going to wonder: do F-algebras themselves form a category, where the objects are F-algebras? Well, we need to determine what the arrows are (and don’t forget that an F-algebra is itself an arrow…).

- -

Let be a category, and let be an endofunctor. If $A$ and $B$ are two objects in , then we have two F-algebras, and . Let’s define a mapping between $A$ and $B$: $z: A \rightarrow B$. So far, we can summarize all these crazy arrows in a diagram:

- - - -

I added the action of $\textbf{F}$ on $z$ already. Now, if this diagram is to commute, both paths from to have to be equal; in other words:

- - - -

If $z$ satisfies this equation, it’s called an F-homomorphism since it preserves the structure of the F-algebra.

- -

Now, since the identity arrow is a homomorphism, and composing two homomorphisms gives another homomorphism, that means that F-algebras themselves form a category. Here’s where it gets interesting. For a general class of functor, this category of F-algebras has an initial object–an initial algebra. I’ll choose for the initial algebra: .

- -

So now we have the same diagram as above, except I’ll label with the initial algebra:

- - - -

And there it is! The catamorphism, which I’ve labeled as $g^\star$, is the unique homomorphism from the initial algebra to any other F-algebra. In the bananas, lenses, and barbed wire paper, it’s defined with banana brackets, which I don’t know how to generate. Oh well! Its definition is:

- - - -

That’s…very abstract. How is this useful? The utility here comes from thinking of the initial algebra as a data type. Let’s pick one: lists in Haskell. A list of type $A$ is either the empty list or a value of type $A$ attached (cons‘ed, if you will) to the back of a list. In other words:

- -
data List a = Nil | Cons a (List a)
-
- -

This will be our . More explicitly, we can write the endofunctor as . Here, $1$ represents the empty list, and $A \times X$ represents the Cartesian product of a type $A$ connected to a type $X$, or the non-empty list, and $+$ is a disjoint sum, sort of like the | in Haskell. Equations like are awesome because they imply that F-algebras encode a notion of self-similarity, like a fractal. In fact, there is a correspondence between these data types and least fixed points; for more on this, check out the great article at FP Complete.

- -

Fun fact: The term catamorphism was coined by Lambert Meertens. The “cata” in “catamorphism”, according to Grant Malcolm, supposedly means “according to” or “following”, so a catamorphism “follows” the structure of the data type. On the other hand, it could also mean “downward”, like “catabolic” or “catastrophic”, and a catamorphism does “plunge into” a data structure. Since it could mean either, but the former is the primary meaning, let’s go with that!

- -

So now we can define a mapping from the initial algebra and, say, the natural numbers. We can define the length of a list, a function from lists to integers. Because a catamorphism is a homomorphism, it will preserve the structure of the list type; our function needs to figure out what to map Nil to (in length’s case, 0), and what to map Cons to (in this case, addition). Because a catamorphism is a mapping from an initial algebra, we thus know that it’s the unique solution.

- -

Thus, if we have the following two equations to solve:

- -
z Nil = c
-z (Cons a as) = f a (z as)
-
- -

where we transform Nil to c and Cons to f (both binary functions), then we immediately know that z is a catamophism, and the unique solution to those equations is given by the catamorphism diagram above. In fact, functional programmers are already familiar with this function: it’s just good ol’ foldr in Haskell:

- -
z = foldr f c
-
- -

Fusion Techniques!

- -

So we’ve discussed catamorphisms on lists, and the definition generalizes to any category where we can define an initial algebra: the natural numbers, binary trees, multiway trees; the sky is the limit. The catamorphism will look different with other data types, but its definition is the same. This is why category theory is useful–do a lot of abstract work once and watch it pay off again and again.

- -

Probably the most useful thing about catamorphisms, though, is fusion, when you can combine a mapping with a catamorphism and get another catamorphism. This could, for example, make a computation more efficient, or make intermediate transformations in a compiler more efficient.

- -

You know what? By now, you’re a pro at reading diagrams, so let’s look at the one that represents fusion:

- - - -

The bottom square commutes and is the same one as before. The upper square defines another F-homomorphism. If both squares commute, the whole rectangle commutes. The fusion law is immediately read off from the diagram:

- - - -

This fuses two separate operations into one–it folds two folds into one!

- -

Further Reading

- -

Okay, so that was probably more than 15 minutes of reading. But now you can dive deeper into any of the sources listed throughout this post. A lot of the literature is in the realm of theses, drafts, and papers with the notable exception of Algebra of Programming, which I drew heavily upon in writing this. I highly recommend that last one!

- -
- - -
-
- - - - - - - - - - - -
- - - - - - - Comments - -
- - - - - -
-

Comments

-
-
-
-
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/blog/archives/index.html b/blog/archives/index.html deleted file mode 100644 index 180ccfd..0000000 --- a/blog/archives/index.html +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - - - Blog Archives | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -

2014

- - - - - - - -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/blog/categories/2-28/atom.xml b/blog/categories/2-28/atom.xml deleted file mode 100644 index 9299228..0000000 --- a/blog/categories/2-28/atom.xml +++ /dev/null @@ -1,153 +0,0 @@ - - - - <![CDATA[Category: 2-28 | Stack Unwinding]]> - - - 2014-02-02T18:42:05-05:00 - http://chrislambda.github.io/ - - - - - Octopress - - - - <![CDATA[From Coding to Calculating]]> - - 2014-01-20T17:18:10-05:00 - http://chrislambda.github.io/blog/2014/01/20/from-coding-to-calculating - Like many aspiring software developers and computer scientists, I wanted to improve my art by going through Structure and Interpretation of Computer Programs. I fully expected to emerge from it all with Sherlock Holmes-esque abilities in programming. Yeah, that was about 7 years ago. Still, every so often, I’ll do some of the exercises and search online to see how my answers compared to others’.

- - - -

Because I didn’t spend a ton of time on these problems, I usually only implemented a naive solution and could only marvel at others’ much cooler, more clever, more efficient solutions. But I could never be sure if they were equivalent. Or could I?

- -

Here’s the example that inspired me to write this post, from SICP:

- -
-

Exercise 2.28. Write a procedure fringe that takes as argument a tree (represented as a list) and returns a list whose elements are all the leaves of the tree arranged in left-to-right order. For example,

-
- -
(define x (list (list 1 2) (list 3 4)))
-
-(fringe x)
-;; equals (1 2 3 4)
-
-(fringe (list x x))
-;; equals (1 2 3 4 1 2 3 4)
-
- -

If you’re not a Scheme user, this function takes something that looks like ((1 2) (3 4)) and gives something that looks like (1 2 3 4). It “gets the numbers out of the parentheses”. The nifty thing about this function is that it will do this for arbitrarily-nested trees, not just one level deep.

- -

This function also shows up in Paul Graham’s On Lisp, where it’s called flatten. In Let Over Lambda, Doug Hoyte points out that since Lisp code is represented by Lisp trees, flatten performs code-walking.

- -

How do you solve a problem like this? One way is to reason about the kinds of values your tree/list can be, and what your function should do with the tree/list when it assumes those values. In Scheme, a list can represent a binary tree. If tr is a tree, then (car tr) gives you the left branch and (cdr tr) gives you the right branch. So thinking recursively, let’s say someone’s already flattened the left and right branch. What now? Well, just append the flattened-left branch to the flattened-right branch and you’re done.

- -

I regard trees as being empty, being a leaf, or being a branch with two leaves. We covered the latter case just above, so let’s handle the other two:

- -
    -
  • -

    Empty, represented by the empty list (). What does a flattened empty list look like? That’s right: just the empty list, so return that.

    -
  • -
  • -

    A leaf. We want the function to return a list though, so return a list containing this value (i.e., wrap it in parentheses).

    -
  • -
- -

That’s all three cases! Here’s a naive solution in Common Lisp:

- -
(defun flatten (tree)
-	(cond ((null tree) nil)
-	      ((atom tree) (cons tree nil))
-	      (t (append (flatten (car tree))
-		         (flatten (cdr tree))))))
-
- -

Lisp code, so beautiful! Here, null tests for the empty list and atom tests to see if the tree is a leaf.

- -

This is all well and good, and it satisfies the test cases from SICP. But if you search for other answers to this question, or even look at the definition in On Lisp, there’s one that looks a bit different:

- -
(defun flatten (tree)
-   (labels ((rec (x acc)
-      (cond ((null x) acc)
-                ((atom x) (cons x acc))
-                (t (rec (car x) (rec (cdr x) acc))))))
-    (rec tree nil)))
-
- -

Here, labels introduces a local helper function in Common Lisp called rec that takes an accumulator. Except for this, it appears to be of the same “form” as the naive solution. It’s not immediately obvious to me how this second solution is equivalent to the first one. Even though I understood the logic of the second solution, I don’t think I would have come up with it on my own, even though I’m familiar with the accumulator technique.

- -

I wanted to not have to be clever. Even more, I wanted to be able to prove the equivalence of any other version of this function. What I wanted was to derive these equivalences in the same way I proved theorems in geometry or performed manipulations in algebra.

- -

Enter Haskell.

- -

I had been reading about Haskell here and there, and I appreciated that you could reason about programs, even calculate them. This is the method described in the book Algebra of Programming, which uses the Gofer language, an ancestor of Haskell. So I’ll recast this in Haskell because its syntax is ultra-clean. First, let’s roll out our own tree data type:

- -
data Tree a = Empty | Leaf a | Branch (Tree a) (Tree a)
-
- -

A tree can be either empty, a leaf holding a value of type a, or a branch of two trees, just like we said above. The flatten function translates into Haskell easily:

- -
flatten :: Tree a -> [a]
-flatten Empty = []
-flatten (Leaf a) = [a]
-flatten (Branch l r) = flatten l ++ flatten r
-
- -

Here, [a] means a list with one value in it, and ++ means “append”. The declaration at the top tells us this function takes a Tree holding values of type a and returns a list. Here’s an example of the function in action:

- -
flatten (Branch (Branch (Leaf 2) (Branch (Branch (Leaf 11) (Empty)) (Leaf 7))) (Leaf 4))
-[2,11,7,4]
-
- -

To make this look more like the second solution, let’s use the accumulator technique, the benefits of which I’ll go over in another post. So we define a helper function, flat_helper, that takes an accumulator as its second value:

- -
flat_helper :: Tree a -> [a] -> [a]
-flat_helper tree list = flatten tree ++ list
-
- -

We flatten tree, and then push the result onto list. I chose this form to “accumulate” the result into list. We can achieve our final result as follows:

- -
flat_helper tree [] = flatten tree ++ [] = flatten tree
-
- -

What does flat_helper do to the tree? The great thing about this is that we can just mechanically derive what flat_helper does from its definition:

- -
flat_helper Empty lst =
-  { definition of flat_helper }
-flatten Empty ++ lst =
-  { definition of flatten }
-[] ++ lst =
-lst
-
-flat_helper (Leaf a) lst =
-  { definition of flat_helper }
-flatten (Leaf a) ++ lst =
-  { definition of flatten }
-[a] ++ lst
-
-flat_helper (Branch l r) lst = 
-  { definition of flat_helper }
-flatten (Branch l r) ++ lst =
-  { definition of flatten }
-(flatten l ++ flatten r) ++ lst =
-  { associativity of append }
-flatten l ++ (flatten r ++ lst) =
-  { definition of flat_helper }
-flatten l ++ flat_helper r lst = 
-  { definition of flat_helper }
-flat_helper l (flat_helper r lst)
-
- -

Et voila! This is the same code from the second solution written in Haskell. I derived one solution from the other in a way that’s almost mathematical. It turns out that it’s more efficient than the naive solution, and we don’t even need to use ++. I wish I could learn from programs like this all the time.

- -

I based the above derivation from a chapter in Introduction to Functional Programming in Haskell, which has several more derivations like the above (I used their bracket comment notation). They called it synthesizing the program, which is a good way to think about it.

- -

I started out using computers to do work I would find tedious or unfeasible to do on my own–iterating over millions of variables, or printing a report every hour. Now I can use them to help me think about computers. A lot of fun for one problem in SICP, I’d say!

- -]]>
-
- -
diff --git a/blog/categories/2-28/index.html b/blog/categories/2-28/index.html deleted file mode 100644 index 6f4c8d2..0000000 --- a/blog/categories/2-28/index.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - Category: 2-28 | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -

2014

- - - -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/blog/categories/algebra/atom.xml b/blog/categories/algebra/atom.xml deleted file mode 100644 index 55b9906..0000000 --- a/blog/categories/algebra/atom.xml +++ /dev/null @@ -1,243 +0,0 @@ - - - - <![CDATA[Category: algebra | Stack Unwinding]]> - - - 2014-02-02T18:42:05-05:00 - http://chrislambda.github.io/ - - - - - Octopress - - - - <![CDATA[Catamorphisms in 15 Minutes!]]> - - 2014-01-30T20:53:57-05:00 - http://chrislambda.github.io/blog/2014/01/30/catamorphisms-in-15-minutes - Let’s say you have a list of numbers. What can you do with it? Find its length, map a function over it, add them up, etc. Wouldn’t it be great if there were one function to rule them all, a function that generalizes what you can do with a list? In many languages, there is a higher-order function called fold that encapsulates a pattern of structural recursion over recursive structures: to specify a function on a list, you specify two exhaustive cases: what to do to an empty list, and what to do with any other list. A generalization of this generalization is the notion of a catamorphism, which is what I’m writing about today.

- - - -

Since catamorphisms are so general, this is really going to be a whirlwind tour through category theory, winding up talking about catas near the end. This is all non-rigorous and hand-wavey, but I hope you can at least hear these concepts later and they make a little bit of sense.

- -

There are many resources about fold (see Graham Hutton’s tutorial) and catamorphisms (see, for example, Edward Kmett’s knol). I wanted to slightly expand on some elementary details from these works. So let’s jump in!

- -

The Shortest Category Theory Tutorial in the World

- -

A category consists of:

- -
    -
  • a collection of objects, such as , and
  • -
  • arrows or morphisms, such as , between objects. You can read this as “ is an arrow from to ”.
  • -
- -

In addition to this structure, objects and arrows must satisfy the following properties:

- -
    -
  • If and , then there must be an arrow from to . This arrow is the composition of and and is written .
  • -
  • For each object , there is an identity arrow from to itself: .
  • -
- - - -

Both of these requirements are shown in the diagram. Each object has an (unlabeled) identity arrow. Notice how I can get from to by either going from to and then from to , or going “directly” there with the composed arrow. This “doesn’t matter which path you take” property of the diagram is called commutation, and so this diagram commutes. Diagrams in category theory are paper tools: they summarize whole paragraphs of proofs in one, maybe complicated figure.

- -

Composition must be associative, which means . So we can ignore the parentheses! The identity arrows take an arrow back to itself: and if $f: A \rightarrow B$.

- -

There. Now you know what category theory is. It’s useful because lots of mathematical structures are categories, like sets, groups, algebras, and so on.

- -

Pro tip: whenever I think of a category, I pretend that I’m really talking about sets to get some intuiton. In the category of sets, set elements are the objects and regular old functions / mappings are the arrows. Beware though: in general, arrows don’t always behave like functions!

- -

Surround your pullquote like this {" text to be quoted "}

- -

Functors

- -

The name “functor” has always sounded like a villain from a sci-fi novel, which probably explains why I find them so interesting.

- -

I already mentioned what a category was. What would happen if I could make some kind of mapping between categories? Whoa. I know, amazing, right? This mapping is called a functor and it transforms a diagram into another diagram.

- -

The neat thing is, even if the objects in the mapped category are completely different from the original category, the structure of the functor’d category is the same as the original’s. I’ve just described a homomorphism, which is a mapping that preserves structure (it means “same form” in Greek). Since a functor acts on a whole category, you have to describe its action on both objects and arrows. Let be a functor from category to category . Then:

- -
    -
  • gets mapped to
  • -
  • get mapped to
  • -
- -

Everyone uses the same symbol to refer to both the arrow mapping and the object mapping. The functor laws analogous to the requirements of a category are:

- -
    -
  • for all objects $A$
  • -
  • for all arrows $f$ and $g$.
  • -
- -

Or, in diagrams (adapted from the XyJax page):

- - - -

Gorgeous. Let’s say what a functor is one more time: a homomorphism between categories.

- -

If you program in Haskell, you’ve probably already heard of its Functor typeclass, which has the following definition:

- -
class Functor f where
-	fmap :: (a -> b) -> f a -> f b
-
- -

This means that when you create a functor in Haskell, you need to define what the fmap function does, and from its signature, I see that it takes functions of type $A \rightarrow B$ to $\textbf{F}A \rightarrow \textbf{F}B$, similar to what we discussed above. See the discussion in Learn You A Haskell for more functor fun.

- -

From these dizzying heights of thinking, let’s think about special objects or functors that will be useful in the goal of generally processing data types like lists as mentioned earlier.

- -

Initial Objects

- -

In some categories, you can imagine that there are some objects that are in some sense “unspecified” or “empty”. These objects are special, like {}, the empty set (the set with the least amount of useful information), or the empty list. These objects share the trait of being unique: there’s only one empty set, only one number such that when you add it to any other number, you get that number back (zero), and so on. In category theory, we can think of this trait more generally, where it’s called initiality. The term “initial” makes sense to me since I think of the category of sets “starting off” with the empty set, for example.

- -

An object is initial if, for any object in the category, there is only one mapping from it to that object. I’ll call the initial object in a category $\textbf{0}$. So in other words, for an object $A$, there is only one arrow of the form $\textbf{0} \rightarrow A$.

- -

Can there be more than one initial object? Let’s pretend the answer is yes and see what that would imply. Let $\textbf{0}$ and $\textbf{0’}$ be initial objects. Then there’s an arrow and :

- - - -

From the diagram, it’s clear that starting from $f$ and coming back through $g$ is a function from $\textbf{0}$ to itself: $g \circ f: \textbf{0} \rightarrow \textbf{0}$. But here’s the trick: since $\textbf{0}$ is initial, there’s only one object from it to any object–including $\textbf{0}$!

- -

So we have that (this is the identity arrow for $\textbf{0}$). I could have started with $\textbf{0’}$ instead, and we would have gotten . So $\textbf{0}$ and $\textbf{0’}$ are basically the same object; the technical term is that they are isomorphic, which means “equal form”. Isomorphisms are sort of like the category-theoretic version of equality.

- -

How does all this help us with our goal of understanding structural recursion and recursive structures? Well, the uniqueness of initial objects (up to isomorphism) is similar to a uniqueness proof: if we come up with some function or procedure on a list, we want to know that it’s the solution (or at least how many there are, even if it’s zero). Functors give us the notion taking a data type to another data type. Let’s combine initial objects and functors and get: F-algebras and catamorphisms!

- -

Mighty Catamorphisms

- -

I very much hope the title of this section made you think of a certain 90’s kid’s show. You’re welcome.

- -

Here’s an example of a recursive structure: non-negative integers! Yes, ever since you learned to count, you’ve been thinking recursively. Good for you! So a non-negative integer is one of two things: zero, or 1 plus another non-negative integer. Or in Haskell, you could write:

- -
data NonNeg = Zero | OnePlus NonNeg
-
- -

So what does “2” look like here? Did you guess OnePlus(OnePlus Zero)? Great! We can reason this through since 2 is either 0 (nope) or 1 plus another number. Well what’s 1? Either 0 (nope again) or 1 plus another number 0. Well what’s 0? Either 0–yep! And now we’re done.

- -

Now, I wouldn’t want to represent the number 839921 like this, but it shows that we can define a useful structure recursively–and sometimes, this may be the best way to do so.

- -

Say, now that I think about it, NonNeg is an example of a category! What we’re interested in is some kind of transformation from this category to itself, and by “transformation” I mean “functor”. A functor that maps a category into itself is called an endofunctor (endo- meaning “inside” in Greek).

- -

But we want a little more than that. In the example involving “2” above, we wanted to transform it into either 0 or 1 plus another number. Or if you’re doing addition, we want an mapping that takes pairs of numbers into another number. So we need arrows with signatures like: , where $A$ here could mean a type, like natural numbers with addition, or NonNeg.

- -

Congrats! You’ve just learned what an F-algebra is: an arrow with type .

- -

By now, you know what I’m going to wonder: do F-algebras themselves form a category, where the objects are F-algebras? Well, we need to determine what the arrows are (and don’t forget that an F-algebra is itself an arrow…).

- -

Let be a category, and let be an endofunctor. If $A$ and $B$ are two objects in , then we have two F-algebras, and . Let’s define a mapping between $A$ and $B$: $z: A \rightarrow B$. So far, we can summarize all these crazy arrows in a diagram:

- - - -

I added the action of $\textbf{F}$ on $z$ already. Now, if this diagram is to commute, both paths from to have to be equal; in other words:

- - - -

If $z$ satisfies this equation, it’s called an F-homomorphism since it preserves the structure of the F-algebra.

- -

Now, since the identity arrow is a homomorphism, and composing two homomorphisms gives another homomorphism, that means that F-algebras themselves form a category. Here’s where it gets interesting. For a general class of functor, this category of F-algebras has an initial object–an initial algebra. I’ll choose for the initial algebra: .

- -

So now we have the same diagram as above, except I’ll label with the initial algebra:

- - - -

And there it is! The catamorphism, which I’ve labeled as $g^\star$, is the unique homomorphism from the initial algebra to any other F-algebra. In the bananas, lenses, and barbed wire paper, it’s defined with banana brackets, which I don’t know how to generate. Oh well! Its definition is:

- - - -

That’s…very abstract. How is this useful? The utility here comes from thinking of the initial algebra as a data type. Let’s pick one: lists in Haskell. A list of type $A$ is either the empty list or a value of type $A$ attached (cons‘ed, if you will) to the back of a list. In other words:

- -
data List a = Nil | Cons a (List a)
-
- -

This will be our . More explicitly, we can write the endofunctor as . Here, $1$ represents the empty list, and $A \times X$ represents the Cartesian product of a type $A$ connected to a type $X$, or the non-empty list, and $+$ is a disjoint sum, sort of like the | in Haskell. Equations like are awesome because they imply that F-algebras encode a notion of self-similarity, like a fractal. In fact, there is a correspondence between these data types and least fixed points; for more on this, check out the great article at FP Complete.

- -

Fun fact: The term catamorphism was coined by Lambert Meertens. The “cata” in “catamorphism”, according to Grant Malcolm, supposedly means “according to” or “following”, so a catamorphism “follows” the structure of the data type. On the other hand, it could also mean “downward”, like “catabolic” or “catastrophic”, and a catamorphism does “plunge into” a data structure. Since it could mean either, but the former is the primary meaning, let’s go with that!

- -

So now we can define a mapping from the initial algebra and, say, the natural numbers. We can define the length of a list, a function from lists to integers. Because a catamorphism is a homomorphism, it will preserve the structure of the list type; our function needs to figure out what to map Nil to (in length’s case, 0), and what to map Cons to (in this case, addition). Because a catamorphism is a mapping from an initial algebra, we thus know that it’s the unique solution.

- -

Thus, if we have the following two equations to solve:

- -
z Nil = c
-z (Cons a as) = f a (z as)
-
- -

where we transform Nil to c and Cons to f (both binary functions), then we immediately know that z is a catamophism, and the unique solution to those equations is given by the catamorphism diagram above. In fact, functional programmers are already familiar with this function: it’s just good ol’ foldr in Haskell:

- -
z = foldr f c
-
- -

Fusion Techniques!

- -

So we’ve discussed catamorphisms on lists, and the definition generalizes to any category where we can define an initial algebra: the natural numbers, binary trees, multiway trees; the sky is the limit. The catamorphism will look different with other data types, but its definition is the same. This is why category theory is useful–do a lot of abstract work once and watch it pay off again and again.

- -

Probably the most useful thing about catamorphisms, though, is fusion, when you can combine a mapping with a catamorphism and get another catamorphism. This could, for example, make a computation more efficient, or make intermediate transformations in a compiler more efficient.

- -

You know what? By now, you’re a pro at reading diagrams, so let’s look at the one that represents fusion:

- - - -

The bottom square commutes and is the same one as before. The upper square defines another F-homomorphism. If both squares commute, the whole rectangle commutes. The fusion law is immediately read off from the diagram:

- - - -

This fuses two separate operations into one–it folds two folds into one!

- -

Further Reading

- -

Okay, so that was probably more than 15 minutes of reading. But now you can dive deeper into any of the sources listed throughout this post. A lot of the literature is in the realm of theses, drafts, and papers with the notable exception of Algebra of Programming, which I drew heavily upon in writing this. I highly recommend that last one!

- -]]>
-
- -
diff --git a/blog/categories/algebra/index.html b/blog/categories/algebra/index.html deleted file mode 100644 index 1383219..0000000 --- a/blog/categories/algebra/index.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - Category: algebra | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -

2014

- - - -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/blog/categories/catamorphism/atom.xml b/blog/categories/catamorphism/atom.xml deleted file mode 100644 index dfa9150..0000000 --- a/blog/categories/catamorphism/atom.xml +++ /dev/null @@ -1,243 +0,0 @@ - - - - <![CDATA[Category: catamorphism | Stack Unwinding]]> - - - 2014-02-02T18:42:05-05:00 - http://chrislambda.github.io/ - - - - - Octopress - - - - <![CDATA[Catamorphisms in 15 Minutes!]]> - - 2014-01-30T20:53:57-05:00 - http://chrislambda.github.io/blog/2014/01/30/catamorphisms-in-15-minutes - Let’s say you have a list of numbers. What can you do with it? Find its length, map a function over it, add them up, etc. Wouldn’t it be great if there were one function to rule them all, a function that generalizes what you can do with a list? In many languages, there is a higher-order function called fold that encapsulates a pattern of structural recursion over recursive structures: to specify a function on a list, you specify two exhaustive cases: what to do to an empty list, and what to do with any other list. A generalization of this generalization is the notion of a catamorphism, which is what I’m writing about today.

- - - -

Since catamorphisms are so general, this is really going to be a whirlwind tour through category theory, winding up talking about catas near the end. This is all non-rigorous and hand-wavey, but I hope you can at least hear these concepts later and they make a little bit of sense.

- -

There are many resources about fold (see Graham Hutton’s tutorial) and catamorphisms (see, for example, Edward Kmett’s knol). I wanted to slightly expand on some elementary details from these works. So let’s jump in!

- -

The Shortest Category Theory Tutorial in the World

- -

A category consists of:

- -
    -
  • a collection of objects, such as , and
  • -
  • arrows or morphisms, such as , between objects. You can read this as “ is an arrow from to ”.
  • -
- -

In addition to this structure, objects and arrows must satisfy the following properties:

- -
    -
  • If and , then there must be an arrow from to . This arrow is the composition of and and is written .
  • -
  • For each object , there is an identity arrow from to itself: .
  • -
- - - -

Both of these requirements are shown in the diagram. Each object has an (unlabeled) identity arrow. Notice how I can get from to by either going from to and then from to , or going “directly” there with the composed arrow. This “doesn’t matter which path you take” property of the diagram is called commutation, and so this diagram commutes. Diagrams in category theory are paper tools: they summarize whole paragraphs of proofs in one, maybe complicated figure.

- -

Composition must be associative, which means . So we can ignore the parentheses! The identity arrows take an arrow back to itself: and if $f: A \rightarrow B$.

- -

There. Now you know what category theory is. It’s useful because lots of mathematical structures are categories, like sets, groups, algebras, and so on.

- -

Pro tip: whenever I think of a category, I pretend that I’m really talking about sets to get some intuiton. In the category of sets, set elements are the objects and regular old functions / mappings are the arrows. Beware though: in general, arrows don’t always behave like functions!

- -

Surround your pullquote like this {" text to be quoted "}

- -

Functors

- -

The name “functor” has always sounded like a villain from a sci-fi novel, which probably explains why I find them so interesting.

- -

I already mentioned what a category was. What would happen if I could make some kind of mapping between categories? Whoa. I know, amazing, right? This mapping is called a functor and it transforms a diagram into another diagram.

- -

The neat thing is, even if the objects in the mapped category are completely different from the original category, the structure of the functor’d category is the same as the original’s. I’ve just described a homomorphism, which is a mapping that preserves structure (it means “same form” in Greek). Since a functor acts on a whole category, you have to describe its action on both objects and arrows. Let be a functor from category to category . Then:

- -
    -
  • gets mapped to
  • -
  • get mapped to
  • -
- -

Everyone uses the same symbol to refer to both the arrow mapping and the object mapping. The functor laws analogous to the requirements of a category are:

- -
    -
  • for all objects $A$
  • -
  • for all arrows $f$ and $g$.
  • -
- -

Or, in diagrams (adapted from the XyJax page):

- - - -

Gorgeous. Let’s say what a functor is one more time: a homomorphism between categories.

- -

If you program in Haskell, you’ve probably already heard of its Functor typeclass, which has the following definition:

- -
class Functor f where
-	fmap :: (a -> b) -> f a -> f b
-
- -

This means that when you create a functor in Haskell, you need to define what the fmap function does, and from its signature, I see that it takes functions of type $A \rightarrow B$ to $\textbf{F}A \rightarrow \textbf{F}B$, similar to what we discussed above. See the discussion in Learn You A Haskell for more functor fun.

- -

From these dizzying heights of thinking, let’s think about special objects or functors that will be useful in the goal of generally processing data types like lists as mentioned earlier.

- -

Initial Objects

- -

In some categories, you can imagine that there are some objects that are in some sense “unspecified” or “empty”. These objects are special, like {}, the empty set (the set with the least amount of useful information), or the empty list. These objects share the trait of being unique: there’s only one empty set, only one number such that when you add it to any other number, you get that number back (zero), and so on. In category theory, we can think of this trait more generally, where it’s called initiality. The term “initial” makes sense to me since I think of the category of sets “starting off” with the empty set, for example.

- -

An object is initial if, for any object in the category, there is only one mapping from it to that object. I’ll call the initial object in a category $\textbf{0}$. So in other words, for an object $A$, there is only one arrow of the form $\textbf{0} \rightarrow A$.

- -

Can there be more than one initial object? Let’s pretend the answer is yes and see what that would imply. Let $\textbf{0}$ and $\textbf{0’}$ be initial objects. Then there’s an arrow and :

- - - -

From the diagram, it’s clear that starting from $f$ and coming back through $g$ is a function from $\textbf{0}$ to itself: $g \circ f: \textbf{0} \rightarrow \textbf{0}$. But here’s the trick: since $\textbf{0}$ is initial, there’s only one object from it to any object–including $\textbf{0}$!

- -

So we have that (this is the identity arrow for $\textbf{0}$). I could have started with $\textbf{0’}$ instead, and we would have gotten . So $\textbf{0}$ and $\textbf{0’}$ are basically the same object; the technical term is that they are isomorphic, which means “equal form”. Isomorphisms are sort of like the category-theoretic version of equality.

- -

How does all this help us with our goal of understanding structural recursion and recursive structures? Well, the uniqueness of initial objects (up to isomorphism) is similar to a uniqueness proof: if we come up with some function or procedure on a list, we want to know that it’s the solution (or at least how many there are, even if it’s zero). Functors give us the notion taking a data type to another data type. Let’s combine initial objects and functors and get: F-algebras and catamorphisms!

- -

Mighty Catamorphisms

- -

I very much hope the title of this section made you think of a certain 90’s kid’s show. You’re welcome.

- -

Here’s an example of a recursive structure: non-negative integers! Yes, ever since you learned to count, you’ve been thinking recursively. Good for you! So a non-negative integer is one of two things: zero, or 1 plus another non-negative integer. Or in Haskell, you could write:

- -
data NonNeg = Zero | OnePlus NonNeg
-
- -

So what does “2” look like here? Did you guess OnePlus(OnePlus Zero)? Great! We can reason this through since 2 is either 0 (nope) or 1 plus another number. Well what’s 1? Either 0 (nope again) or 1 plus another number 0. Well what’s 0? Either 0–yep! And now we’re done.

- -

Now, I wouldn’t want to represent the number 839921 like this, but it shows that we can define a useful structure recursively–and sometimes, this may be the best way to do so.

- -

Say, now that I think about it, NonNeg is an example of a category! What we’re interested in is some kind of transformation from this category to itself, and by “transformation” I mean “functor”. A functor that maps a category into itself is called an endofunctor (endo- meaning “inside” in Greek).

- -

But we want a little more than that. In the example involving “2” above, we wanted to transform it into either 0 or 1 plus another number. Or if you’re doing addition, we want an mapping that takes pairs of numbers into another number. So we need arrows with signatures like: , where $A$ here could mean a type, like natural numbers with addition, or NonNeg.

- -

Congrats! You’ve just learned what an F-algebra is: an arrow with type .

- -

By now, you know what I’m going to wonder: do F-algebras themselves form a category, where the objects are F-algebras? Well, we need to determine what the arrows are (and don’t forget that an F-algebra is itself an arrow…).

- -

Let be a category, and let be an endofunctor. If $A$ and $B$ are two objects in , then we have two F-algebras, and . Let’s define a mapping between $A$ and $B$: $z: A \rightarrow B$. So far, we can summarize all these crazy arrows in a diagram:

- - - -

I added the action of $\textbf{F}$ on $z$ already. Now, if this diagram is to commute, both paths from to have to be equal; in other words:

- - - -

If $z$ satisfies this equation, it’s called an F-homomorphism since it preserves the structure of the F-algebra.

- -

Now, since the identity arrow is a homomorphism, and composing two homomorphisms gives another homomorphism, that means that F-algebras themselves form a category. Here’s where it gets interesting. For a general class of functor, this category of F-algebras has an initial object–an initial algebra. I’ll choose for the initial algebra: .

- -

So now we have the same diagram as above, except I’ll label with the initial algebra:

- - - -

And there it is! The catamorphism, which I’ve labeled as $g^\star$, is the unique homomorphism from the initial algebra to any other F-algebra. In the bananas, lenses, and barbed wire paper, it’s defined with banana brackets, which I don’t know how to generate. Oh well! Its definition is:

- - - -

That’s…very abstract. How is this useful? The utility here comes from thinking of the initial algebra as a data type. Let’s pick one: lists in Haskell. A list of type $A$ is either the empty list or a value of type $A$ attached (cons‘ed, if you will) to the back of a list. In other words:

- -
data List a = Nil | Cons a (List a)
-
- -

This will be our . More explicitly, we can write the endofunctor as . Here, $1$ represents the empty list, and $A \times X$ represents the Cartesian product of a type $A$ connected to a type $X$, or the non-empty list, and $+$ is a disjoint sum, sort of like the | in Haskell. Equations like are awesome because they imply that F-algebras encode a notion of self-similarity, like a fractal. In fact, there is a correspondence between these data types and least fixed points; for more on this, check out the great article at FP Complete.

- -

Fun fact: The term catamorphism was coined by Lambert Meertens. The “cata” in “catamorphism”, according to Grant Malcolm, supposedly means “according to” or “following”, so a catamorphism “follows” the structure of the data type. On the other hand, it could also mean “downward”, like “catabolic” or “catastrophic”, and a catamorphism does “plunge into” a data structure. Since it could mean either, but the former is the primary meaning, let’s go with that!

- -

So now we can define a mapping from the initial algebra and, say, the natural numbers. We can define the length of a list, a function from lists to integers. Because a catamorphism is a homomorphism, it will preserve the structure of the list type; our function needs to figure out what to map Nil to (in length’s case, 0), and what to map Cons to (in this case, addition). Because a catamorphism is a mapping from an initial algebra, we thus know that it’s the unique solution.

- -

Thus, if we have the following two equations to solve:

- -
z Nil = c
-z (Cons a as) = f a (z as)
-
- -

where we transform Nil to c and Cons to f (both binary functions), then we immediately know that z is a catamophism, and the unique solution to those equations is given by the catamorphism diagram above. In fact, functional programmers are already familiar with this function: it’s just good ol’ foldr in Haskell:

- -
z = foldr f c
-
- -

Fusion Techniques!

- -

So we’ve discussed catamorphisms on lists, and the definition generalizes to any category where we can define an initial algebra: the natural numbers, binary trees, multiway trees; the sky is the limit. The catamorphism will look different with other data types, but its definition is the same. This is why category theory is useful–do a lot of abstract work once and watch it pay off again and again.

- -

Probably the most useful thing about catamorphisms, though, is fusion, when you can combine a mapping with a catamorphism and get another catamorphism. This could, for example, make a computation more efficient, or make intermediate transformations in a compiler more efficient.

- -

You know what? By now, you’re a pro at reading diagrams, so let’s look at the one that represents fusion:

- - - -

The bottom square commutes and is the same one as before. The upper square defines another F-homomorphism. If both squares commute, the whole rectangle commutes. The fusion law is immediately read off from the diagram:

- - - -

This fuses two separate operations into one–it folds two folds into one!

- -

Further Reading

- -

Okay, so that was probably more than 15 minutes of reading. But now you can dive deeper into any of the sources listed throughout this post. A lot of the literature is in the realm of theses, drafts, and papers with the notable exception of Algebra of Programming, which I drew heavily upon in writing this. I highly recommend that last one!

- -]]>
-
- -
diff --git a/blog/categories/catamorphism/index.html b/blog/categories/catamorphism/index.html deleted file mode 100644 index f749d65..0000000 --- a/blog/categories/catamorphism/index.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - Category: catamorphism | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -

2014

- - - -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/blog/categories/category/atom.xml b/blog/categories/category/atom.xml deleted file mode 100644 index 0001a52..0000000 --- a/blog/categories/category/atom.xml +++ /dev/null @@ -1,243 +0,0 @@ - - - - <![CDATA[Category: category | Stack Unwinding]]> - - - 2014-02-02T18:42:05-05:00 - http://chrislambda.github.io/ - - - - - Octopress - - - - <![CDATA[Catamorphisms in 15 Minutes!]]> - - 2014-01-30T20:53:57-05:00 - http://chrislambda.github.io/blog/2014/01/30/catamorphisms-in-15-minutes - Let’s say you have a list of numbers. What can you do with it? Find its length, map a function over it, add them up, etc. Wouldn’t it be great if there were one function to rule them all, a function that generalizes what you can do with a list? In many languages, there is a higher-order function called fold that encapsulates a pattern of structural recursion over recursive structures: to specify a function on a list, you specify two exhaustive cases: what to do to an empty list, and what to do with any other list. A generalization of this generalization is the notion of a catamorphism, which is what I’m writing about today.

- - - -

Since catamorphisms are so general, this is really going to be a whirlwind tour through category theory, winding up talking about catas near the end. This is all non-rigorous and hand-wavey, but I hope you can at least hear these concepts later and they make a little bit of sense.

- -

There are many resources about fold (see Graham Hutton’s tutorial) and catamorphisms (see, for example, Edward Kmett’s knol). I wanted to slightly expand on some elementary details from these works. So let’s jump in!

- -

The Shortest Category Theory Tutorial in the World

- -

A category consists of:

- -
    -
  • a collection of objects, such as , and
  • -
  • arrows or morphisms, such as , between objects. You can read this as “ is an arrow from to ”.
  • -
- -

In addition to this structure, objects and arrows must satisfy the following properties:

- -
    -
  • If and , then there must be an arrow from to . This arrow is the composition of and and is written .
  • -
  • For each object , there is an identity arrow from to itself: .
  • -
- - - -

Both of these requirements are shown in the diagram. Each object has an (unlabeled) identity arrow. Notice how I can get from to by either going from to and then from to , or going “directly” there with the composed arrow. This “doesn’t matter which path you take” property of the diagram is called commutation, and so this diagram commutes. Diagrams in category theory are paper tools: they summarize whole paragraphs of proofs in one, maybe complicated figure.

- -

Composition must be associative, which means . So we can ignore the parentheses! The identity arrows take an arrow back to itself: and if $f: A \rightarrow B$.

- -

There. Now you know what category theory is. It’s useful because lots of mathematical structures are categories, like sets, groups, algebras, and so on.

- -

Pro tip: whenever I think of a category, I pretend that I’m really talking about sets to get some intuiton. In the category of sets, set elements are the objects and regular old functions / mappings are the arrows. Beware though: in general, arrows don’t always behave like functions!

- -

Surround your pullquote like this {" text to be quoted "}

- -

Functors

- -

The name “functor” has always sounded like a villain from a sci-fi novel, which probably explains why I find them so interesting.

- -

I already mentioned what a category was. What would happen if I could make some kind of mapping between categories? Whoa. I know, amazing, right? This mapping is called a functor and it transforms a diagram into another diagram.

- -

The neat thing is, even if the objects in the mapped category are completely different from the original category, the structure of the functor’d category is the same as the original’s. I’ve just described a homomorphism, which is a mapping that preserves structure (it means “same form” in Greek). Since a functor acts on a whole category, you have to describe its action on both objects and arrows. Let be a functor from category to category . Then:

- -
    -
  • gets mapped to
  • -
  • get mapped to
  • -
- -

Everyone uses the same symbol to refer to both the arrow mapping and the object mapping. The functor laws analogous to the requirements of a category are:

- -
    -
  • for all objects $A$
  • -
  • for all arrows $f$ and $g$.
  • -
- -

Or, in diagrams (adapted from the XyJax page):

- - - -

Gorgeous. Let’s say what a functor is one more time: a homomorphism between categories.

- -

If you program in Haskell, you’ve probably already heard of its Functor typeclass, which has the following definition:

- -
class Functor f where
-	fmap :: (a -> b) -> f a -> f b
-
- -

This means that when you create a functor in Haskell, you need to define what the fmap function does, and from its signature, I see that it takes functions of type $A \rightarrow B$ to $\textbf{F}A \rightarrow \textbf{F}B$, similar to what we discussed above. See the discussion in Learn You A Haskell for more functor fun.

- -

From these dizzying heights of thinking, let’s think about special objects or functors that will be useful in the goal of generally processing data types like lists as mentioned earlier.

- -

Initial Objects

- -

In some categories, you can imagine that there are some objects that are in some sense “unspecified” or “empty”. These objects are special, like {}, the empty set (the set with the least amount of useful information), or the empty list. These objects share the trait of being unique: there’s only one empty set, only one number such that when you add it to any other number, you get that number back (zero), and so on. In category theory, we can think of this trait more generally, where it’s called initiality. The term “initial” makes sense to me since I think of the category of sets “starting off” with the empty set, for example.

- -

An object is initial if, for any object in the category, there is only one mapping from it to that object. I’ll call the initial object in a category $\textbf{0}$. So in other words, for an object $A$, there is only one arrow of the form $\textbf{0} \rightarrow A$.

- -

Can there be more than one initial object? Let’s pretend the answer is yes and see what that would imply. Let $\textbf{0}$ and $\textbf{0’}$ be initial objects. Then there’s an arrow and :

- - - -

From the diagram, it’s clear that starting from $f$ and coming back through $g$ is a function from $\textbf{0}$ to itself: $g \circ f: \textbf{0} \rightarrow \textbf{0}$. But here’s the trick: since $\textbf{0}$ is initial, there’s only one object from it to any object–including $\textbf{0}$!

- -

So we have that (this is the identity arrow for $\textbf{0}$). I could have started with $\textbf{0’}$ instead, and we would have gotten . So $\textbf{0}$ and $\textbf{0’}$ are basically the same object; the technical term is that they are isomorphic, which means “equal form”. Isomorphisms are sort of like the category-theoretic version of equality.

- -

How does all this help us with our goal of understanding structural recursion and recursive structures? Well, the uniqueness of initial objects (up to isomorphism) is similar to a uniqueness proof: if we come up with some function or procedure on a list, we want to know that it’s the solution (or at least how many there are, even if it’s zero). Functors give us the notion taking a data type to another data type. Let’s combine initial objects and functors and get: F-algebras and catamorphisms!

- -

Mighty Catamorphisms

- -

I very much hope the title of this section made you think of a certain 90’s kid’s show. You’re welcome.

- -

Here’s an example of a recursive structure: non-negative integers! Yes, ever since you learned to count, you’ve been thinking recursively. Good for you! So a non-negative integer is one of two things: zero, or 1 plus another non-negative integer. Or in Haskell, you could write:

- -
data NonNeg = Zero | OnePlus NonNeg
-
- -

So what does “2” look like here? Did you guess OnePlus(OnePlus Zero)? Great! We can reason this through since 2 is either 0 (nope) or 1 plus another number. Well what’s 1? Either 0 (nope again) or 1 plus another number 0. Well what’s 0? Either 0–yep! And now we’re done.

- -

Now, I wouldn’t want to represent the number 839921 like this, but it shows that we can define a useful structure recursively–and sometimes, this may be the best way to do so.

- -

Say, now that I think about it, NonNeg is an example of a category! What we’re interested in is some kind of transformation from this category to itself, and by “transformation” I mean “functor”. A functor that maps a category into itself is called an endofunctor (endo- meaning “inside” in Greek).

- -

But we want a little more than that. In the example involving “2” above, we wanted to transform it into either 0 or 1 plus another number. Or if you’re doing addition, we want an mapping that takes pairs of numbers into another number. So we need arrows with signatures like: , where $A$ here could mean a type, like natural numbers with addition, or NonNeg.

- -

Congrats! You’ve just learned what an F-algebra is: an arrow with type .

- -

By now, you know what I’m going to wonder: do F-algebras themselves form a category, where the objects are F-algebras? Well, we need to determine what the arrows are (and don’t forget that an F-algebra is itself an arrow…).

- -

Let be a category, and let be an endofunctor. If $A$ and $B$ are two objects in , then we have two F-algebras, and . Let’s define a mapping between $A$ and $B$: $z: A \rightarrow B$. So far, we can summarize all these crazy arrows in a diagram:

- - - -

I added the action of $\textbf{F}$ on $z$ already. Now, if this diagram is to commute, both paths from to have to be equal; in other words:

- - - -

If $z$ satisfies this equation, it’s called an F-homomorphism since it preserves the structure of the F-algebra.

- -

Now, since the identity arrow is a homomorphism, and composing two homomorphisms gives another homomorphism, that means that F-algebras themselves form a category. Here’s where it gets interesting. For a general class of functor, this category of F-algebras has an initial object–an initial algebra. I’ll choose for the initial algebra: .

- -

So now we have the same diagram as above, except I’ll label with the initial algebra:

- - - -

And there it is! The catamorphism, which I’ve labeled as $g^\star$, is the unique homomorphism from the initial algebra to any other F-algebra. In the bananas, lenses, and barbed wire paper, it’s defined with banana brackets, which I don’t know how to generate. Oh well! Its definition is:

- - - -

That’s…very abstract. How is this useful? The utility here comes from thinking of the initial algebra as a data type. Let’s pick one: lists in Haskell. A list of type $A$ is either the empty list or a value of type $A$ attached (cons‘ed, if you will) to the back of a list. In other words:

- -
data List a = Nil | Cons a (List a)
-
- -

This will be our . More explicitly, we can write the endofunctor as . Here, $1$ represents the empty list, and $A \times X$ represents the Cartesian product of a type $A$ connected to a type $X$, or the non-empty list, and $+$ is a disjoint sum, sort of like the | in Haskell. Equations like are awesome because they imply that F-algebras encode a notion of self-similarity, like a fractal. In fact, there is a correspondence between these data types and least fixed points; for more on this, check out the great article at FP Complete.

- -

Fun fact: The term catamorphism was coined by Lambert Meertens. The “cata” in “catamorphism”, according to Grant Malcolm, supposedly means “according to” or “following”, so a catamorphism “follows” the structure of the data type. On the other hand, it could also mean “downward”, like “catabolic” or “catastrophic”, and a catamorphism does “plunge into” a data structure. Since it could mean either, but the former is the primary meaning, let’s go with that!

- -

So now we can define a mapping from the initial algebra and, say, the natural numbers. We can define the length of a list, a function from lists to integers. Because a catamorphism is a homomorphism, it will preserve the structure of the list type; our function needs to figure out what to map Nil to (in length’s case, 0), and what to map Cons to (in this case, addition). Because a catamorphism is a mapping from an initial algebra, we thus know that it’s the unique solution.

- -

Thus, if we have the following two equations to solve:

- -
z Nil = c
-z (Cons a as) = f a (z as)
-
- -

where we transform Nil to c and Cons to f (both binary functions), then we immediately know that z is a catamophism, and the unique solution to those equations is given by the catamorphism diagram above. In fact, functional programmers are already familiar with this function: it’s just good ol’ foldr in Haskell:

- -
z = foldr f c
-
- -

Fusion Techniques!

- -

So we’ve discussed catamorphisms on lists, and the definition generalizes to any category where we can define an initial algebra: the natural numbers, binary trees, multiway trees; the sky is the limit. The catamorphism will look different with other data types, but its definition is the same. This is why category theory is useful–do a lot of abstract work once and watch it pay off again and again.

- -

Probably the most useful thing about catamorphisms, though, is fusion, when you can combine a mapping with a catamorphism and get another catamorphism. This could, for example, make a computation more efficient, or make intermediate transformations in a compiler more efficient.

- -

You know what? By now, you’re a pro at reading diagrams, so let’s look at the one that represents fusion:

- - - -

The bottom square commutes and is the same one as before. The upper square defines another F-homomorphism. If both squares commute, the whole rectangle commutes. The fusion law is immediately read off from the diagram:

- - - -

This fuses two separate operations into one–it folds two folds into one!

- -

Further Reading

- -

Okay, so that was probably more than 15 minutes of reading. But now you can dive deeper into any of the sources listed throughout this post. A lot of the literature is in the realm of theses, drafts, and papers with the notable exception of Algebra of Programming, which I drew heavily upon in writing this. I highly recommend that last one!

- -]]>
-
- -
diff --git a/blog/categories/category/index.html b/blog/categories/category/index.html deleted file mode 100644 index ebeaa14..0000000 --- a/blog/categories/category/index.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - Category: category | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -

2014

- - - -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/blog/categories/functional/atom.xml b/blog/categories/functional/atom.xml deleted file mode 100644 index 1f919fb..0000000 --- a/blog/categories/functional/atom.xml +++ /dev/null @@ -1,380 +0,0 @@ - - - - <![CDATA[Category: functional | Stack Unwinding]]> - - - 2014-02-02T18:42:05-05:00 - http://chrislambda.github.io/ - - - - - Octopress - - - - <![CDATA[Catamorphisms in 15 Minutes!]]> - - 2014-01-30T20:53:57-05:00 - http://chrislambda.github.io/blog/2014/01/30/catamorphisms-in-15-minutes - Let’s say you have a list of numbers. What can you do with it? Find its length, map a function over it, add them up, etc. Wouldn’t it be great if there were one function to rule them all, a function that generalizes what you can do with a list? In many languages, there is a higher-order function called fold that encapsulates a pattern of structural recursion over recursive structures: to specify a function on a list, you specify two exhaustive cases: what to do to an empty list, and what to do with any other list. A generalization of this generalization is the notion of a catamorphism, which is what I’m writing about today.

- - - -

Since catamorphisms are so general, this is really going to be a whirlwind tour through category theory, winding up talking about catas near the end. This is all non-rigorous and hand-wavey, but I hope you can at least hear these concepts later and they make a little bit of sense.

- -

There are many resources about fold (see Graham Hutton’s tutorial) and catamorphisms (see, for example, Edward Kmett’s knol). I wanted to slightly expand on some elementary details from these works. So let’s jump in!

- -

The Shortest Category Theory Tutorial in the World

- -

A category consists of:

- -
    -
  • a collection of objects, such as , and
  • -
  • arrows or morphisms, such as , between objects. You can read this as “ is an arrow from to ”.
  • -
- -

In addition to this structure, objects and arrows must satisfy the following properties:

- -
    -
  • If and , then there must be an arrow from to . This arrow is the composition of and and is written .
  • -
  • For each object , there is an identity arrow from to itself: .
  • -
- - - -

Both of these requirements are shown in the diagram. Each object has an (unlabeled) identity arrow. Notice how I can get from to by either going from to and then from to , or going “directly” there with the composed arrow. This “doesn’t matter which path you take” property of the diagram is called commutation, and so this diagram commutes. Diagrams in category theory are paper tools: they summarize whole paragraphs of proofs in one, maybe complicated figure.

- -

Composition must be associative, which means . So we can ignore the parentheses! The identity arrows take an arrow back to itself: and if $f: A \rightarrow B$.

- -

There. Now you know what category theory is. It’s useful because lots of mathematical structures are categories, like sets, groups, algebras, and so on.

- -

Pro tip: whenever I think of a category, I pretend that I’m really talking about sets to get some intuiton. In the category of sets, set elements are the objects and regular old functions / mappings are the arrows. Beware though: in general, arrows don’t always behave like functions!

- -

Surround your pullquote like this {" text to be quoted "}

- -

Functors

- -

The name “functor” has always sounded like a villain from a sci-fi novel, which probably explains why I find them so interesting.

- -

I already mentioned what a category was. What would happen if I could make some kind of mapping between categories? Whoa. I know, amazing, right? This mapping is called a functor and it transforms a diagram into another diagram.

- -

The neat thing is, even if the objects in the mapped category are completely different from the original category, the structure of the functor’d category is the same as the original’s. I’ve just described a homomorphism, which is a mapping that preserves structure (it means “same form” in Greek). Since a functor acts on a whole category, you have to describe its action on both objects and arrows. Let be a functor from category to category . Then:

- -
    -
  • gets mapped to
  • -
  • get mapped to
  • -
- -

Everyone uses the same symbol to refer to both the arrow mapping and the object mapping. The functor laws analogous to the requirements of a category are:

- -
    -
  • for all objects $A$
  • -
  • for all arrows $f$ and $g$.
  • -
- -

Or, in diagrams (adapted from the XyJax page):

- - - -

Gorgeous. Let’s say what a functor is one more time: a homomorphism between categories.

- -

If you program in Haskell, you’ve probably already heard of its Functor typeclass, which has the following definition:

- -
class Functor f where
-	fmap :: (a -> b) -> f a -> f b
-
- -

This means that when you create a functor in Haskell, you need to define what the fmap function does, and from its signature, I see that it takes functions of type $A \rightarrow B$ to $\textbf{F}A \rightarrow \textbf{F}B$, similar to what we discussed above. See the discussion in Learn You A Haskell for more functor fun.

- -

From these dizzying heights of thinking, let’s think about special objects or functors that will be useful in the goal of generally processing data types like lists as mentioned earlier.

- -

Initial Objects

- -

In some categories, you can imagine that there are some objects that are in some sense “unspecified” or “empty”. These objects are special, like {}, the empty set (the set with the least amount of useful information), or the empty list. These objects share the trait of being unique: there’s only one empty set, only one number such that when you add it to any other number, you get that number back (zero), and so on. In category theory, we can think of this trait more generally, where it’s called initiality. The term “initial” makes sense to me since I think of the category of sets “starting off” with the empty set, for example.

- -

An object is initial if, for any object in the category, there is only one mapping from it to that object. I’ll call the initial object in a category $\textbf{0}$. So in other words, for an object $A$, there is only one arrow of the form $\textbf{0} \rightarrow A$.

- -

Can there be more than one initial object? Let’s pretend the answer is yes and see what that would imply. Let $\textbf{0}$ and $\textbf{0’}$ be initial objects. Then there’s an arrow and :

- - - -

From the diagram, it’s clear that starting from $f$ and coming back through $g$ is a function from $\textbf{0}$ to itself: $g \circ f: \textbf{0} \rightarrow \textbf{0}$. But here’s the trick: since $\textbf{0}$ is initial, there’s only one object from it to any object–including $\textbf{0}$!

- -

So we have that (this is the identity arrow for $\textbf{0}$). I could have started with $\textbf{0’}$ instead, and we would have gotten . So $\textbf{0}$ and $\textbf{0’}$ are basically the same object; the technical term is that they are isomorphic, which means “equal form”. Isomorphisms are sort of like the category-theoretic version of equality.

- -

How does all this help us with our goal of understanding structural recursion and recursive structures? Well, the uniqueness of initial objects (up to isomorphism) is similar to a uniqueness proof: if we come up with some function or procedure on a list, we want to know that it’s the solution (or at least how many there are, even if it’s zero). Functors give us the notion taking a data type to another data type. Let’s combine initial objects and functors and get: F-algebras and catamorphisms!

- -

Mighty Catamorphisms

- -

I very much hope the title of this section made you think of a certain 90’s kid’s show. You’re welcome.

- -

Here’s an example of a recursive structure: non-negative integers! Yes, ever since you learned to count, you’ve been thinking recursively. Good for you! So a non-negative integer is one of two things: zero, or 1 plus another non-negative integer. Or in Haskell, you could write:

- -
data NonNeg = Zero | OnePlus NonNeg
-
- -

So what does “2” look like here? Did you guess OnePlus(OnePlus Zero)? Great! We can reason this through since 2 is either 0 (nope) or 1 plus another number. Well what’s 1? Either 0 (nope again) or 1 plus another number 0. Well what’s 0? Either 0–yep! And now we’re done.

- -

Now, I wouldn’t want to represent the number 839921 like this, but it shows that we can define a useful structure recursively–and sometimes, this may be the best way to do so.

- -

Say, now that I think about it, NonNeg is an example of a category! What we’re interested in is some kind of transformation from this category to itself, and by “transformation” I mean “functor”. A functor that maps a category into itself is called an endofunctor (endo- meaning “inside” in Greek).

- -

But we want a little more than that. In the example involving “2” above, we wanted to transform it into either 0 or 1 plus another number. Or if you’re doing addition, we want an mapping that takes pairs of numbers into another number. So we need arrows with signatures like: , where $A$ here could mean a type, like natural numbers with addition, or NonNeg.

- -

Congrats! You’ve just learned what an F-algebra is: an arrow with type .

- -

By now, you know what I’m going to wonder: do F-algebras themselves form a category, where the objects are F-algebras? Well, we need to determine what the arrows are (and don’t forget that an F-algebra is itself an arrow…).

- -

Let be a category, and let be an endofunctor. If $A$ and $B$ are two objects in , then we have two F-algebras, and . Let’s define a mapping between $A$ and $B$: $z: A \rightarrow B$. So far, we can summarize all these crazy arrows in a diagram:

- - - -

I added the action of $\textbf{F}$ on $z$ already. Now, if this diagram is to commute, both paths from to have to be equal; in other words:

- - - -

If $z$ satisfies this equation, it’s called an F-homomorphism since it preserves the structure of the F-algebra.

- -

Now, since the identity arrow is a homomorphism, and composing two homomorphisms gives another homomorphism, that means that F-algebras themselves form a category. Here’s where it gets interesting. For a general class of functor, this category of F-algebras has an initial object–an initial algebra. I’ll choose for the initial algebra: .

- -

So now we have the same diagram as above, except I’ll label with the initial algebra:

- - - -

And there it is! The catamorphism, which I’ve labeled as $g^\star$, is the unique homomorphism from the initial algebra to any other F-algebra. In the bananas, lenses, and barbed wire paper, it’s defined with banana brackets, which I don’t know how to generate. Oh well! Its definition is:

- - - -

That’s…very abstract. How is this useful? The utility here comes from thinking of the initial algebra as a data type. Let’s pick one: lists in Haskell. A list of type $A$ is either the empty list or a value of type $A$ attached (cons‘ed, if you will) to the back of a list. In other words:

- -
data List a = Nil | Cons a (List a)
-
- -

This will be our . More explicitly, we can write the endofunctor as . Here, $1$ represents the empty list, and $A \times X$ represents the Cartesian product of a type $A$ connected to a type $X$, or the non-empty list, and $+$ is a disjoint sum, sort of like the | in Haskell. Equations like are awesome because they imply that F-algebras encode a notion of self-similarity, like a fractal. In fact, there is a correspondence between these data types and least fixed points; for more on this, check out the great article at FP Complete.

- -

Fun fact: The term catamorphism was coined by Lambert Meertens. The “cata” in “catamorphism”, according to Grant Malcolm, supposedly means “according to” or “following”, so a catamorphism “follows” the structure of the data type. On the other hand, it could also mean “downward”, like “catabolic” or “catastrophic”, and a catamorphism does “plunge into” a data structure. Since it could mean either, but the former is the primary meaning, let’s go with that!

- -

So now we can define a mapping from the initial algebra and, say, the natural numbers. We can define the length of a list, a function from lists to integers. Because a catamorphism is a homomorphism, it will preserve the structure of the list type; our function needs to figure out what to map Nil to (in length’s case, 0), and what to map Cons to (in this case, addition). Because a catamorphism is a mapping from an initial algebra, we thus know that it’s the unique solution.

- -

Thus, if we have the following two equations to solve:

- -
z Nil = c
-z (Cons a as) = f a (z as)
-
- -

where we transform Nil to c and Cons to f (both binary functions), then we immediately know that z is a catamophism, and the unique solution to those equations is given by the catamorphism diagram above. In fact, functional programmers are already familiar with this function: it’s just good ol’ foldr in Haskell:

- -
z = foldr f c
-
- -

Fusion Techniques!

- -

So we’ve discussed catamorphisms on lists, and the definition generalizes to any category where we can define an initial algebra: the natural numbers, binary trees, multiway trees; the sky is the limit. The catamorphism will look different with other data types, but its definition is the same. This is why category theory is useful–do a lot of abstract work once and watch it pay off again and again.

- -

Probably the most useful thing about catamorphisms, though, is fusion, when you can combine a mapping with a catamorphism and get another catamorphism. This could, for example, make a computation more efficient, or make intermediate transformations in a compiler more efficient.

- -

You know what? By now, you’re a pro at reading diagrams, so let’s look at the one that represents fusion:

- - - -

The bottom square commutes and is the same one as before. The upper square defines another F-homomorphism. If both squares commute, the whole rectangle commutes. The fusion law is immediately read off from the diagram:

- - - -

This fuses two separate operations into one–it folds two folds into one!

- -

Further Reading

- -

Okay, so that was probably more than 15 minutes of reading. But now you can dive deeper into any of the sources listed throughout this post. A lot of the literature is in the realm of theses, drafts, and papers with the notable exception of Algebra of Programming, which I drew heavily upon in writing this. I highly recommend that last one!

- -]]>
-
- - - <![CDATA[From Coding to Calculating]]> - - 2014-01-20T17:18:10-05:00 - http://chrislambda.github.io/blog/2014/01/20/from-coding-to-calculating - Like many aspiring software developers and computer scientists, I wanted to improve my art by going through Structure and Interpretation of Computer Programs. I fully expected to emerge from it all with Sherlock Holmes-esque abilities in programming. Yeah, that was about 7 years ago. Still, every so often, I’ll do some of the exercises and search online to see how my answers compared to others’.

- - - -

Because I didn’t spend a ton of time on these problems, I usually only implemented a naive solution and could only marvel at others’ much cooler, more clever, more efficient solutions. But I could never be sure if they were equivalent. Or could I?

- -

Here’s the example that inspired me to write this post, from SICP:

- -
-

Exercise 2.28. Write a procedure fringe that takes as argument a tree (represented as a list) and returns a list whose elements are all the leaves of the tree arranged in left-to-right order. For example,

-
- -
(define x (list (list 1 2) (list 3 4)))
-
-(fringe x)
-;; equals (1 2 3 4)
-
-(fringe (list x x))
-;; equals (1 2 3 4 1 2 3 4)
-
- -

If you’re not a Scheme user, this function takes something that looks like ((1 2) (3 4)) and gives something that looks like (1 2 3 4). It “gets the numbers out of the parentheses”. The nifty thing about this function is that it will do this for arbitrarily-nested trees, not just one level deep.

- -

This function also shows up in Paul Graham’s On Lisp, where it’s called flatten. In Let Over Lambda, Doug Hoyte points out that since Lisp code is represented by Lisp trees, flatten performs code-walking.

- -

How do you solve a problem like this? One way is to reason about the kinds of values your tree/list can be, and what your function should do with the tree/list when it assumes those values. In Scheme, a list can represent a binary tree. If tr is a tree, then (car tr) gives you the left branch and (cdr tr) gives you the right branch. So thinking recursively, let’s say someone’s already flattened the left and right branch. What now? Well, just append the flattened-left branch to the flattened-right branch and you’re done.

- -

I regard trees as being empty, being a leaf, or being a branch with two leaves. We covered the latter case just above, so let’s handle the other two:

- -
    -
  • -

    Empty, represented by the empty list (). What does a flattened empty list look like? That’s right: just the empty list, so return that.

    -
  • -
  • -

    A leaf. We want the function to return a list though, so return a list containing this value (i.e., wrap it in parentheses).

    -
  • -
- -

That’s all three cases! Here’s a naive solution in Common Lisp:

- -
(defun flatten (tree)
-	(cond ((null tree) nil)
-	      ((atom tree) (cons tree nil))
-	      (t (append (flatten (car tree))
-		         (flatten (cdr tree))))))
-
- -

Lisp code, so beautiful! Here, null tests for the empty list and atom tests to see if the tree is a leaf.

- -

This is all well and good, and it satisfies the test cases from SICP. But if you search for other answers to this question, or even look at the definition in On Lisp, there’s one that looks a bit different:

- -
(defun flatten (tree)
-   (labels ((rec (x acc)
-      (cond ((null x) acc)
-                ((atom x) (cons x acc))
-                (t (rec (car x) (rec (cdr x) acc))))))
-    (rec tree nil)))
-
- -

Here, labels introduces a local helper function in Common Lisp called rec that takes an accumulator. Except for this, it appears to be of the same “form” as the naive solution. It’s not immediately obvious to me how this second solution is equivalent to the first one. Even though I understood the logic of the second solution, I don’t think I would have come up with it on my own, even though I’m familiar with the accumulator technique.

- -

I wanted to not have to be clever. Even more, I wanted to be able to prove the equivalence of any other version of this function. What I wanted was to derive these equivalences in the same way I proved theorems in geometry or performed manipulations in algebra.

- -

Enter Haskell.

- -

I had been reading about Haskell here and there, and I appreciated that you could reason about programs, even calculate them. This is the method described in the book Algebra of Programming, which uses the Gofer language, an ancestor of Haskell. So I’ll recast this in Haskell because its syntax is ultra-clean. First, let’s roll out our own tree data type:

- -
data Tree a = Empty | Leaf a | Branch (Tree a) (Tree a)
-
- -

A tree can be either empty, a leaf holding a value of type a, or a branch of two trees, just like we said above. The flatten function translates into Haskell easily:

- -
flatten :: Tree a -> [a]
-flatten Empty = []
-flatten (Leaf a) = [a]
-flatten (Branch l r) = flatten l ++ flatten r
-
- -

Here, [a] means a list with one value in it, and ++ means “append”. The declaration at the top tells us this function takes a Tree holding values of type a and returns a list. Here’s an example of the function in action:

- -
flatten (Branch (Branch (Leaf 2) (Branch (Branch (Leaf 11) (Empty)) (Leaf 7))) (Leaf 4))
-[2,11,7,4]
-
- -

To make this look more like the second solution, let’s use the accumulator technique, the benefits of which I’ll go over in another post. So we define a helper function, flat_helper, that takes an accumulator as its second value:

- -
flat_helper :: Tree a -> [a] -> [a]
-flat_helper tree list = flatten tree ++ list
-
- -

We flatten tree, and then push the result onto list. I chose this form to “accumulate” the result into list. We can achieve our final result as follows:

- -
flat_helper tree [] = flatten tree ++ [] = flatten tree
-
- -

What does flat_helper do to the tree? The great thing about this is that we can just mechanically derive what flat_helper does from its definition:

- -
flat_helper Empty lst =
-  { definition of flat_helper }
-flatten Empty ++ lst =
-  { definition of flatten }
-[] ++ lst =
-lst
-
-flat_helper (Leaf a) lst =
-  { definition of flat_helper }
-flatten (Leaf a) ++ lst =
-  { definition of flatten }
-[a] ++ lst
-
-flat_helper (Branch l r) lst = 
-  { definition of flat_helper }
-flatten (Branch l r) ++ lst =
-  { definition of flatten }
-(flatten l ++ flatten r) ++ lst =
-  { associativity of append }
-flatten l ++ (flatten r ++ lst) =
-  { definition of flat_helper }
-flatten l ++ flat_helper r lst = 
-  { definition of flat_helper }
-flat_helper l (flat_helper r lst)
-
- -

Et voila! This is the same code from the second solution written in Haskell. I derived one solution from the other in a way that’s almost mathematical. It turns out that it’s more efficient than the naive solution, and we don’t even need to use ++. I wish I could learn from programs like this all the time.

- -

I based the above derivation from a chapter in Introduction to Functional Programming in Haskell, which has several more derivations like the above (I used their bracket comment notation). They called it synthesizing the program, which is a good way to think about it.

- -

I started out using computers to do work I would find tedious or unfeasible to do on my own–iterating over millions of variables, or printing a report every hour. Now I can use them to help me think about computers. A lot of fun for one problem in SICP, I’d say!

- -]]>
-
- -
diff --git a/blog/categories/functional/index.html b/blog/categories/functional/index.html deleted file mode 100644 index 0593d16..0000000 --- a/blog/categories/functional/index.html +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - - - Category: functional | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -

2014

- - - - - - - - -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/blog/categories/functor/atom.xml b/blog/categories/functor/atom.xml deleted file mode 100644 index 1048558..0000000 --- a/blog/categories/functor/atom.xml +++ /dev/null @@ -1,243 +0,0 @@ - - - - <![CDATA[Category: functor | Stack Unwinding]]> - - - 2014-02-02T18:42:05-05:00 - http://chrislambda.github.io/ - - - - - Octopress - - - - <![CDATA[Catamorphisms in 15 Minutes!]]> - - 2014-01-30T20:53:57-05:00 - http://chrislambda.github.io/blog/2014/01/30/catamorphisms-in-15-minutes - Let’s say you have a list of numbers. What can you do with it? Find its length, map a function over it, add them up, etc. Wouldn’t it be great if there were one function to rule them all, a function that generalizes what you can do with a list? In many languages, there is a higher-order function called fold that encapsulates a pattern of structural recursion over recursive structures: to specify a function on a list, you specify two exhaustive cases: what to do to an empty list, and what to do with any other list. A generalization of this generalization is the notion of a catamorphism, which is what I’m writing about today.

- - - -

Since catamorphisms are so general, this is really going to be a whirlwind tour through category theory, winding up talking about catas near the end. This is all non-rigorous and hand-wavey, but I hope you can at least hear these concepts later and they make a little bit of sense.

- -

There are many resources about fold (see Graham Hutton’s tutorial) and catamorphisms (see, for example, Edward Kmett’s knol). I wanted to slightly expand on some elementary details from these works. So let’s jump in!

- -

The Shortest Category Theory Tutorial in the World

- -

A category consists of:

- -
    -
  • a collection of objects, such as , and
  • -
  • arrows or morphisms, such as , between objects. You can read this as “ is an arrow from to ”.
  • -
- -

In addition to this structure, objects and arrows must satisfy the following properties:

- -
    -
  • If and , then there must be an arrow from to . This arrow is the composition of and and is written .
  • -
  • For each object , there is an identity arrow from to itself: .
  • -
- - - -

Both of these requirements are shown in the diagram. Each object has an (unlabeled) identity arrow. Notice how I can get from to by either going from to and then from to , or going “directly” there with the composed arrow. This “doesn’t matter which path you take” property of the diagram is called commutation, and so this diagram commutes. Diagrams in category theory are paper tools: they summarize whole paragraphs of proofs in one, maybe complicated figure.

- -

Composition must be associative, which means . So we can ignore the parentheses! The identity arrows take an arrow back to itself: and if $f: A \rightarrow B$.

- -

There. Now you know what category theory is. It’s useful because lots of mathematical structures are categories, like sets, groups, algebras, and so on.

- -

Pro tip: whenever I think of a category, I pretend that I’m really talking about sets to get some intuiton. In the category of sets, set elements are the objects and regular old functions / mappings are the arrows. Beware though: in general, arrows don’t always behave like functions!

- -

Surround your pullquote like this {" text to be quoted "}

- -

Functors

- -

The name “functor” has always sounded like a villain from a sci-fi novel, which probably explains why I find them so interesting.

- -

I already mentioned what a category was. What would happen if I could make some kind of mapping between categories? Whoa. I know, amazing, right? This mapping is called a functor and it transforms a diagram into another diagram.

- -

The neat thing is, even if the objects in the mapped category are completely different from the original category, the structure of the functor’d category is the same as the original’s. I’ve just described a homomorphism, which is a mapping that preserves structure (it means “same form” in Greek). Since a functor acts on a whole category, you have to describe its action on both objects and arrows. Let be a functor from category to category . Then:

- -
    -
  • gets mapped to
  • -
  • get mapped to
  • -
- -

Everyone uses the same symbol to refer to both the arrow mapping and the object mapping. The functor laws analogous to the requirements of a category are:

- -
    -
  • for all objects $A$
  • -
  • for all arrows $f$ and $g$.
  • -
- -

Or, in diagrams (adapted from the XyJax page):

- - - -

Gorgeous. Let’s say what a functor is one more time: a homomorphism between categories.

- -

If you program in Haskell, you’ve probably already heard of its Functor typeclass, which has the following definition:

- -
class Functor f where
-	fmap :: (a -> b) -> f a -> f b
-
- -

This means that when you create a functor in Haskell, you need to define what the fmap function does, and from its signature, I see that it takes functions of type $A \rightarrow B$ to $\textbf{F}A \rightarrow \textbf{F}B$, similar to what we discussed above. See the discussion in Learn You A Haskell for more functor fun.

- -

From these dizzying heights of thinking, let’s think about special objects or functors that will be useful in the goal of generally processing data types like lists as mentioned earlier.

- -

Initial Objects

- -

In some categories, you can imagine that there are some objects that are in some sense “unspecified” or “empty”. These objects are special, like {}, the empty set (the set with the least amount of useful information), or the empty list. These objects share the trait of being unique: there’s only one empty set, only one number such that when you add it to any other number, you get that number back (zero), and so on. In category theory, we can think of this trait more generally, where it’s called initiality. The term “initial” makes sense to me since I think of the category of sets “starting off” with the empty set, for example.

- -

An object is initial if, for any object in the category, there is only one mapping from it to that object. I’ll call the initial object in a category $\textbf{0}$. So in other words, for an object $A$, there is only one arrow of the form $\textbf{0} \rightarrow A$.

- -

Can there be more than one initial object? Let’s pretend the answer is yes and see what that would imply. Let $\textbf{0}$ and $\textbf{0’}$ be initial objects. Then there’s an arrow and :

- - - -

From the diagram, it’s clear that starting from $f$ and coming back through $g$ is a function from $\textbf{0}$ to itself: $g \circ f: \textbf{0} \rightarrow \textbf{0}$. But here’s the trick: since $\textbf{0}$ is initial, there’s only one object from it to any object–including $\textbf{0}$!

- -

So we have that (this is the identity arrow for $\textbf{0}$). I could have started with $\textbf{0’}$ instead, and we would have gotten . So $\textbf{0}$ and $\textbf{0’}$ are basically the same object; the technical term is that they are isomorphic, which means “equal form”. Isomorphisms are sort of like the category-theoretic version of equality.

- -

How does all this help us with our goal of understanding structural recursion and recursive structures? Well, the uniqueness of initial objects (up to isomorphism) is similar to a uniqueness proof: if we come up with some function or procedure on a list, we want to know that it’s the solution (or at least how many there are, even if it’s zero). Functors give us the notion taking a data type to another data type. Let’s combine initial objects and functors and get: F-algebras and catamorphisms!

- -

Mighty Catamorphisms

- -

I very much hope the title of this section made you think of a certain 90’s kid’s show. You’re welcome.

- -

Here’s an example of a recursive structure: non-negative integers! Yes, ever since you learned to count, you’ve been thinking recursively. Good for you! So a non-negative integer is one of two things: zero, or 1 plus another non-negative integer. Or in Haskell, you could write:

- -
data NonNeg = Zero | OnePlus NonNeg
-
- -

So what does “2” look like here? Did you guess OnePlus(OnePlus Zero)? Great! We can reason this through since 2 is either 0 (nope) or 1 plus another number. Well what’s 1? Either 0 (nope again) or 1 plus another number 0. Well what’s 0? Either 0–yep! And now we’re done.

- -

Now, I wouldn’t want to represent the number 839921 like this, but it shows that we can define a useful structure recursively–and sometimes, this may be the best way to do so.

- -

Say, now that I think about it, NonNeg is an example of a category! What we’re interested in is some kind of transformation from this category to itself, and by “transformation” I mean “functor”. A functor that maps a category into itself is called an endofunctor (endo- meaning “inside” in Greek).

- -

But we want a little more than that. In the example involving “2” above, we wanted to transform it into either 0 or 1 plus another number. Or if you’re doing addition, we want an mapping that takes pairs of numbers into another number. So we need arrows with signatures like: , where $A$ here could mean a type, like natural numbers with addition, or NonNeg.

- -

Congrats! You’ve just learned what an F-algebra is: an arrow with type .

- -

By now, you know what I’m going to wonder: do F-algebras themselves form a category, where the objects are F-algebras? Well, we need to determine what the arrows are (and don’t forget that an F-algebra is itself an arrow…).

- -

Let be a category, and let be an endofunctor. If $A$ and $B$ are two objects in , then we have two F-algebras, and . Let’s define a mapping between $A$ and $B$: $z: A \rightarrow B$. So far, we can summarize all these crazy arrows in a diagram:

- - - -

I added the action of $\textbf{F}$ on $z$ already. Now, if this diagram is to commute, both paths from to have to be equal; in other words:

- - - -

If $z$ satisfies this equation, it’s called an F-homomorphism since it preserves the structure of the F-algebra.

- -

Now, since the identity arrow is a homomorphism, and composing two homomorphisms gives another homomorphism, that means that F-algebras themselves form a category. Here’s where it gets interesting. For a general class of functor, this category of F-algebras has an initial object–an initial algebra. I’ll choose for the initial algebra: .

- -

So now we have the same diagram as above, except I’ll label with the initial algebra:

- - - -

And there it is! The catamorphism, which I’ve labeled as $g^\star$, is the unique homomorphism from the initial algebra to any other F-algebra. In the bananas, lenses, and barbed wire paper, it’s defined with banana brackets, which I don’t know how to generate. Oh well! Its definition is:

- - - -

That’s…very abstract. How is this useful? The utility here comes from thinking of the initial algebra as a data type. Let’s pick one: lists in Haskell. A list of type $A$ is either the empty list or a value of type $A$ attached (cons‘ed, if you will) to the back of a list. In other words:

- -
data List a = Nil | Cons a (List a)
-
- -

This will be our . More explicitly, we can write the endofunctor as . Here, $1$ represents the empty list, and $A \times X$ represents the Cartesian product of a type $A$ connected to a type $X$, or the non-empty list, and $+$ is a disjoint sum, sort of like the | in Haskell. Equations like are awesome because they imply that F-algebras encode a notion of self-similarity, like a fractal. In fact, there is a correspondence between these data types and least fixed points; for more on this, check out the great article at FP Complete.

- -

Fun fact: The term catamorphism was coined by Lambert Meertens. The “cata” in “catamorphism”, according to Grant Malcolm, supposedly means “according to” or “following”, so a catamorphism “follows” the structure of the data type. On the other hand, it could also mean “downward”, like “catabolic” or “catastrophic”, and a catamorphism does “plunge into” a data structure. Since it could mean either, but the former is the primary meaning, let’s go with that!

- -

So now we can define a mapping from the initial algebra and, say, the natural numbers. We can define the length of a list, a function from lists to integers. Because a catamorphism is a homomorphism, it will preserve the structure of the list type; our function needs to figure out what to map Nil to (in length’s case, 0), and what to map Cons to (in this case, addition). Because a catamorphism is a mapping from an initial algebra, we thus know that it’s the unique solution.

- -

Thus, if we have the following two equations to solve:

- -
z Nil = c
-z (Cons a as) = f a (z as)
-
- -

where we transform Nil to c and Cons to f (both binary functions), then we immediately know that z is a catamophism, and the unique solution to those equations is given by the catamorphism diagram above. In fact, functional programmers are already familiar with this function: it’s just good ol’ foldr in Haskell:

- -
z = foldr f c
-
- -

Fusion Techniques!

- -

So we’ve discussed catamorphisms on lists, and the definition generalizes to any category where we can define an initial algebra: the natural numbers, binary trees, multiway trees; the sky is the limit. The catamorphism will look different with other data types, but its definition is the same. This is why category theory is useful–do a lot of abstract work once and watch it pay off again and again.

- -

Probably the most useful thing about catamorphisms, though, is fusion, when you can combine a mapping with a catamorphism and get another catamorphism. This could, for example, make a computation more efficient, or make intermediate transformations in a compiler more efficient.

- -

You know what? By now, you’re a pro at reading diagrams, so let’s look at the one that represents fusion:

- - - -

The bottom square commutes and is the same one as before. The upper square defines another F-homomorphism. If both squares commute, the whole rectangle commutes. The fusion law is immediately read off from the diagram:

- - - -

This fuses two separate operations into one–it folds two folds into one!

- -

Further Reading

- -

Okay, so that was probably more than 15 minutes of reading. But now you can dive deeper into any of the sources listed throughout this post. A lot of the literature is in the realm of theses, drafts, and papers with the notable exception of Algebra of Programming, which I drew heavily upon in writing this. I highly recommend that last one!

- -]]>
-
- -
diff --git a/blog/categories/functor/index.html b/blog/categories/functor/index.html deleted file mode 100644 index 3faae8e..0000000 --- a/blog/categories/functor/index.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - Category: functor | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -

2014

- - - -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/blog/categories/haskell/atom.xml b/blog/categories/haskell/atom.xml deleted file mode 100644 index c7aa97e..0000000 --- a/blog/categories/haskell/atom.xml +++ /dev/null @@ -1,380 +0,0 @@ - - - - <![CDATA[Category: Haskell | Stack Unwinding]]> - - - 2014-02-02T18:42:05-05:00 - http://chrislambda.github.io/ - - - - - Octopress - - - - <![CDATA[Catamorphisms in 15 Minutes!]]> - - 2014-01-30T20:53:57-05:00 - http://chrislambda.github.io/blog/2014/01/30/catamorphisms-in-15-minutes - Let’s say you have a list of numbers. What can you do with it? Find its length, map a function over it, add them up, etc. Wouldn’t it be great if there were one function to rule them all, a function that generalizes what you can do with a list? In many languages, there is a higher-order function called fold that encapsulates a pattern of structural recursion over recursive structures: to specify a function on a list, you specify two exhaustive cases: what to do to an empty list, and what to do with any other list. A generalization of this generalization is the notion of a catamorphism, which is what I’m writing about today.

- - - -

Since catamorphisms are so general, this is really going to be a whirlwind tour through category theory, winding up talking about catas near the end. This is all non-rigorous and hand-wavey, but I hope you can at least hear these concepts later and they make a little bit of sense.

- -

There are many resources about fold (see Graham Hutton’s tutorial) and catamorphisms (see, for example, Edward Kmett’s knol). I wanted to slightly expand on some elementary details from these works. So let’s jump in!

- -

The Shortest Category Theory Tutorial in the World

- -

A category consists of:

- -
    -
  • a collection of objects, such as , and
  • -
  • arrows or morphisms, such as , between objects. You can read this as “ is an arrow from to ”.
  • -
- -

In addition to this structure, objects and arrows must satisfy the following properties:

- -
    -
  • If and , then there must be an arrow from to . This arrow is the composition of and and is written .
  • -
  • For each object , there is an identity arrow from to itself: .
  • -
- - - -

Both of these requirements are shown in the diagram. Each object has an (unlabeled) identity arrow. Notice how I can get from to by either going from to and then from to , or going “directly” there with the composed arrow. This “doesn’t matter which path you take” property of the diagram is called commutation, and so this diagram commutes. Diagrams in category theory are paper tools: they summarize whole paragraphs of proofs in one, maybe complicated figure.

- -

Composition must be associative, which means . So we can ignore the parentheses! The identity arrows take an arrow back to itself: and if $f: A \rightarrow B$.

- -

There. Now you know what category theory is. It’s useful because lots of mathematical structures are categories, like sets, groups, algebras, and so on.

- -

Pro tip: whenever I think of a category, I pretend that I’m really talking about sets to get some intuiton. In the category of sets, set elements are the objects and regular old functions / mappings are the arrows. Beware though: in general, arrows don’t always behave like functions!

- -

Surround your pullquote like this {" text to be quoted "}

- -

Functors

- -

The name “functor” has always sounded like a villain from a sci-fi novel, which probably explains why I find them so interesting.

- -

I already mentioned what a category was. What would happen if I could make some kind of mapping between categories? Whoa. I know, amazing, right? This mapping is called a functor and it transforms a diagram into another diagram.

- -

The neat thing is, even if the objects in the mapped category are completely different from the original category, the structure of the functor’d category is the same as the original’s. I’ve just described a homomorphism, which is a mapping that preserves structure (it means “same form” in Greek). Since a functor acts on a whole category, you have to describe its action on both objects and arrows. Let be a functor from category to category . Then:

- -
    -
  • gets mapped to
  • -
  • get mapped to
  • -
- -

Everyone uses the same symbol to refer to both the arrow mapping and the object mapping. The functor laws analogous to the requirements of a category are:

- -
    -
  • for all objects $A$
  • -
  • for all arrows $f$ and $g$.
  • -
- -

Or, in diagrams (adapted from the XyJax page):

- - - -

Gorgeous. Let’s say what a functor is one more time: a homomorphism between categories.

- -

If you program in Haskell, you’ve probably already heard of its Functor typeclass, which has the following definition:

- -
class Functor f where
-	fmap :: (a -> b) -> f a -> f b
-
- -

This means that when you create a functor in Haskell, you need to define what the fmap function does, and from its signature, I see that it takes functions of type $A \rightarrow B$ to $\textbf{F}A \rightarrow \textbf{F}B$, similar to what we discussed above. See the discussion in Learn You A Haskell for more functor fun.

- -

From these dizzying heights of thinking, let’s think about special objects or functors that will be useful in the goal of generally processing data types like lists as mentioned earlier.

- -

Initial Objects

- -

In some categories, you can imagine that there are some objects that are in some sense “unspecified” or “empty”. These objects are special, like {}, the empty set (the set with the least amount of useful information), or the empty list. These objects share the trait of being unique: there’s only one empty set, only one number such that when you add it to any other number, you get that number back (zero), and so on. In category theory, we can think of this trait more generally, where it’s called initiality. The term “initial” makes sense to me since I think of the category of sets “starting off” with the empty set, for example.

- -

An object is initial if, for any object in the category, there is only one mapping from it to that object. I’ll call the initial object in a category $\textbf{0}$. So in other words, for an object $A$, there is only one arrow of the form $\textbf{0} \rightarrow A$.

- -

Can there be more than one initial object? Let’s pretend the answer is yes and see what that would imply. Let $\textbf{0}$ and $\textbf{0’}$ be initial objects. Then there’s an arrow and :

- - - -

From the diagram, it’s clear that starting from $f$ and coming back through $g$ is a function from $\textbf{0}$ to itself: $g \circ f: \textbf{0} \rightarrow \textbf{0}$. But here’s the trick: since $\textbf{0}$ is initial, there’s only one object from it to any object–including $\textbf{0}$!

- -

So we have that (this is the identity arrow for $\textbf{0}$). I could have started with $\textbf{0’}$ instead, and we would have gotten . So $\textbf{0}$ and $\textbf{0’}$ are basically the same object; the technical term is that they are isomorphic, which means “equal form”. Isomorphisms are sort of like the category-theoretic version of equality.

- -

How does all this help us with our goal of understanding structural recursion and recursive structures? Well, the uniqueness of initial objects (up to isomorphism) is similar to a uniqueness proof: if we come up with some function or procedure on a list, we want to know that it’s the solution (or at least how many there are, even if it’s zero). Functors give us the notion taking a data type to another data type. Let’s combine initial objects and functors and get: F-algebras and catamorphisms!

- -

Mighty Catamorphisms

- -

I very much hope the title of this section made you think of a certain 90’s kid’s show. You’re welcome.

- -

Here’s an example of a recursive structure: non-negative integers! Yes, ever since you learned to count, you’ve been thinking recursively. Good for you! So a non-negative integer is one of two things: zero, or 1 plus another non-negative integer. Or in Haskell, you could write:

- -
data NonNeg = Zero | OnePlus NonNeg
-
- -

So what does “2” look like here? Did you guess OnePlus(OnePlus Zero)? Great! We can reason this through since 2 is either 0 (nope) or 1 plus another number. Well what’s 1? Either 0 (nope again) or 1 plus another number 0. Well what’s 0? Either 0–yep! And now we’re done.

- -

Now, I wouldn’t want to represent the number 839921 like this, but it shows that we can define a useful structure recursively–and sometimes, this may be the best way to do so.

- -

Say, now that I think about it, NonNeg is an example of a category! What we’re interested in is some kind of transformation from this category to itself, and by “transformation” I mean “functor”. A functor that maps a category into itself is called an endofunctor (endo- meaning “inside” in Greek).

- -

But we want a little more than that. In the example involving “2” above, we wanted to transform it into either 0 or 1 plus another number. Or if you’re doing addition, we want an mapping that takes pairs of numbers into another number. So we need arrows with signatures like: , where $A$ here could mean a type, like natural numbers with addition, or NonNeg.

- -

Congrats! You’ve just learned what an F-algebra is: an arrow with type .

- -

By now, you know what I’m going to wonder: do F-algebras themselves form a category, where the objects are F-algebras? Well, we need to determine what the arrows are (and don’t forget that an F-algebra is itself an arrow…).

- -

Let be a category, and let be an endofunctor. If $A$ and $B$ are two objects in , then we have two F-algebras, and . Let’s define a mapping between $A$ and $B$: $z: A \rightarrow B$. So far, we can summarize all these crazy arrows in a diagram:

- - - -

I added the action of $\textbf{F}$ on $z$ already. Now, if this diagram is to commute, both paths from to have to be equal; in other words:

- - - -

If $z$ satisfies this equation, it’s called an F-homomorphism since it preserves the structure of the F-algebra.

- -

Now, since the identity arrow is a homomorphism, and composing two homomorphisms gives another homomorphism, that means that F-algebras themselves form a category. Here’s where it gets interesting. For a general class of functor, this category of F-algebras has an initial object–an initial algebra. I’ll choose for the initial algebra: .

- -

So now we have the same diagram as above, except I’ll label with the initial algebra:

- - - -

And there it is! The catamorphism, which I’ve labeled as $g^\star$, is the unique homomorphism from the initial algebra to any other F-algebra. In the bananas, lenses, and barbed wire paper, it’s defined with banana brackets, which I don’t know how to generate. Oh well! Its definition is:

- - - -

That’s…very abstract. How is this useful? The utility here comes from thinking of the initial algebra as a data type. Let’s pick one: lists in Haskell. A list of type $A$ is either the empty list or a value of type $A$ attached (cons‘ed, if you will) to the back of a list. In other words:

- -
data List a = Nil | Cons a (List a)
-
- -

This will be our . More explicitly, we can write the endofunctor as . Here, $1$ represents the empty list, and $A \times X$ represents the Cartesian product of a type $A$ connected to a type $X$, or the non-empty list, and $+$ is a disjoint sum, sort of like the | in Haskell. Equations like are awesome because they imply that F-algebras encode a notion of self-similarity, like a fractal. In fact, there is a correspondence between these data types and least fixed points; for more on this, check out the great article at FP Complete.

- -

Fun fact: The term catamorphism was coined by Lambert Meertens. The “cata” in “catamorphism”, according to Grant Malcolm, supposedly means “according to” or “following”, so a catamorphism “follows” the structure of the data type. On the other hand, it could also mean “downward”, like “catabolic” or “catastrophic”, and a catamorphism does “plunge into” a data structure. Since it could mean either, but the former is the primary meaning, let’s go with that!

- -

So now we can define a mapping from the initial algebra and, say, the natural numbers. We can define the length of a list, a function from lists to integers. Because a catamorphism is a homomorphism, it will preserve the structure of the list type; our function needs to figure out what to map Nil to (in length’s case, 0), and what to map Cons to (in this case, addition). Because a catamorphism is a mapping from an initial algebra, we thus know that it’s the unique solution.

- -

Thus, if we have the following two equations to solve:

- -
z Nil = c
-z (Cons a as) = f a (z as)
-
- -

where we transform Nil to c and Cons to f (both binary functions), then we immediately know that z is a catamophism, and the unique solution to those equations is given by the catamorphism diagram above. In fact, functional programmers are already familiar with this function: it’s just good ol’ foldr in Haskell:

- -
z = foldr f c
-
- -

Fusion Techniques!

- -

So we’ve discussed catamorphisms on lists, and the definition generalizes to any category where we can define an initial algebra: the natural numbers, binary trees, multiway trees; the sky is the limit. The catamorphism will look different with other data types, but its definition is the same. This is why category theory is useful–do a lot of abstract work once and watch it pay off again and again.

- -

Probably the most useful thing about catamorphisms, though, is fusion, when you can combine a mapping with a catamorphism and get another catamorphism. This could, for example, make a computation more efficient, or make intermediate transformations in a compiler more efficient.

- -

You know what? By now, you’re a pro at reading diagrams, so let’s look at the one that represents fusion:

- - - -

The bottom square commutes and is the same one as before. The upper square defines another F-homomorphism. If both squares commute, the whole rectangle commutes. The fusion law is immediately read off from the diagram:

- - - -

This fuses two separate operations into one–it folds two folds into one!

- -

Further Reading

- -

Okay, so that was probably more than 15 minutes of reading. But now you can dive deeper into any of the sources listed throughout this post. A lot of the literature is in the realm of theses, drafts, and papers with the notable exception of Algebra of Programming, which I drew heavily upon in writing this. I highly recommend that last one!

- -]]>
-
- - - <![CDATA[From Coding to Calculating]]> - - 2014-01-20T17:18:10-05:00 - http://chrislambda.github.io/blog/2014/01/20/from-coding-to-calculating - Like many aspiring software developers and computer scientists, I wanted to improve my art by going through Structure and Interpretation of Computer Programs. I fully expected to emerge from it all with Sherlock Holmes-esque abilities in programming. Yeah, that was about 7 years ago. Still, every so often, I’ll do some of the exercises and search online to see how my answers compared to others’.

- - - -

Because I didn’t spend a ton of time on these problems, I usually only implemented a naive solution and could only marvel at others’ much cooler, more clever, more efficient solutions. But I could never be sure if they were equivalent. Or could I?

- -

Here’s the example that inspired me to write this post, from SICP:

- -
-

Exercise 2.28. Write a procedure fringe that takes as argument a tree (represented as a list) and returns a list whose elements are all the leaves of the tree arranged in left-to-right order. For example,

-
- -
(define x (list (list 1 2) (list 3 4)))
-
-(fringe x)
-;; equals (1 2 3 4)
-
-(fringe (list x x))
-;; equals (1 2 3 4 1 2 3 4)
-
- -

If you’re not a Scheme user, this function takes something that looks like ((1 2) (3 4)) and gives something that looks like (1 2 3 4). It “gets the numbers out of the parentheses”. The nifty thing about this function is that it will do this for arbitrarily-nested trees, not just one level deep.

- -

This function also shows up in Paul Graham’s On Lisp, where it’s called flatten. In Let Over Lambda, Doug Hoyte points out that since Lisp code is represented by Lisp trees, flatten performs code-walking.

- -

How do you solve a problem like this? One way is to reason about the kinds of values your tree/list can be, and what your function should do with the tree/list when it assumes those values. In Scheme, a list can represent a binary tree. If tr is a tree, then (car tr) gives you the left branch and (cdr tr) gives you the right branch. So thinking recursively, let’s say someone’s already flattened the left and right branch. What now? Well, just append the flattened-left branch to the flattened-right branch and you’re done.

- -

I regard trees as being empty, being a leaf, or being a branch with two leaves. We covered the latter case just above, so let’s handle the other two:

- -
    -
  • -

    Empty, represented by the empty list (). What does a flattened empty list look like? That’s right: just the empty list, so return that.

    -
  • -
  • -

    A leaf. We want the function to return a list though, so return a list containing this value (i.e., wrap it in parentheses).

    -
  • -
- -

That’s all three cases! Here’s a naive solution in Common Lisp:

- -
(defun flatten (tree)
-	(cond ((null tree) nil)
-	      ((atom tree) (cons tree nil))
-	      (t (append (flatten (car tree))
-		         (flatten (cdr tree))))))
-
- -

Lisp code, so beautiful! Here, null tests for the empty list and atom tests to see if the tree is a leaf.

- -

This is all well and good, and it satisfies the test cases from SICP. But if you search for other answers to this question, or even look at the definition in On Lisp, there’s one that looks a bit different:

- -
(defun flatten (tree)
-   (labels ((rec (x acc)
-      (cond ((null x) acc)
-                ((atom x) (cons x acc))
-                (t (rec (car x) (rec (cdr x) acc))))))
-    (rec tree nil)))
-
- -

Here, labels introduces a local helper function in Common Lisp called rec that takes an accumulator. Except for this, it appears to be of the same “form” as the naive solution. It’s not immediately obvious to me how this second solution is equivalent to the first one. Even though I understood the logic of the second solution, I don’t think I would have come up with it on my own, even though I’m familiar with the accumulator technique.

- -

I wanted to not have to be clever. Even more, I wanted to be able to prove the equivalence of any other version of this function. What I wanted was to derive these equivalences in the same way I proved theorems in geometry or performed manipulations in algebra.

- -

Enter Haskell.

- -

I had been reading about Haskell here and there, and I appreciated that you could reason about programs, even calculate them. This is the method described in the book Algebra of Programming, which uses the Gofer language, an ancestor of Haskell. So I’ll recast this in Haskell because its syntax is ultra-clean. First, let’s roll out our own tree data type:

- -
data Tree a = Empty | Leaf a | Branch (Tree a) (Tree a)
-
- -

A tree can be either empty, a leaf holding a value of type a, or a branch of two trees, just like we said above. The flatten function translates into Haskell easily:

- -
flatten :: Tree a -> [a]
-flatten Empty = []
-flatten (Leaf a) = [a]
-flatten (Branch l r) = flatten l ++ flatten r
-
- -

Here, [a] means a list with one value in it, and ++ means “append”. The declaration at the top tells us this function takes a Tree holding values of type a and returns a list. Here’s an example of the function in action:

- -
flatten (Branch (Branch (Leaf 2) (Branch (Branch (Leaf 11) (Empty)) (Leaf 7))) (Leaf 4))
-[2,11,7,4]
-
- -

To make this look more like the second solution, let’s use the accumulator technique, the benefits of which I’ll go over in another post. So we define a helper function, flat_helper, that takes an accumulator as its second value:

- -
flat_helper :: Tree a -> [a] -> [a]
-flat_helper tree list = flatten tree ++ list
-
- -

We flatten tree, and then push the result onto list. I chose this form to “accumulate” the result into list. We can achieve our final result as follows:

- -
flat_helper tree [] = flatten tree ++ [] = flatten tree
-
- -

What does flat_helper do to the tree? The great thing about this is that we can just mechanically derive what flat_helper does from its definition:

- -
flat_helper Empty lst =
-  { definition of flat_helper }
-flatten Empty ++ lst =
-  { definition of flatten }
-[] ++ lst =
-lst
-
-flat_helper (Leaf a) lst =
-  { definition of flat_helper }
-flatten (Leaf a) ++ lst =
-  { definition of flatten }
-[a] ++ lst
-
-flat_helper (Branch l r) lst = 
-  { definition of flat_helper }
-flatten (Branch l r) ++ lst =
-  { definition of flatten }
-(flatten l ++ flatten r) ++ lst =
-  { associativity of append }
-flatten l ++ (flatten r ++ lst) =
-  { definition of flat_helper }
-flatten l ++ flat_helper r lst = 
-  { definition of flat_helper }
-flat_helper l (flat_helper r lst)
-
- -

Et voila! This is the same code from the second solution written in Haskell. I derived one solution from the other in a way that’s almost mathematical. It turns out that it’s more efficient than the naive solution, and we don’t even need to use ++. I wish I could learn from programs like this all the time.

- -

I based the above derivation from a chapter in Introduction to Functional Programming in Haskell, which has several more derivations like the above (I used their bracket comment notation). They called it synthesizing the program, which is a good way to think about it.

- -

I started out using computers to do work I would find tedious or unfeasible to do on my own–iterating over millions of variables, or printing a report every hour. Now I can use them to help me think about computers. A lot of fun for one problem in SICP, I’d say!

- -]]>
-
- -
diff --git a/blog/categories/haskell/index.html b/blog/categories/haskell/index.html deleted file mode 100644 index 6a6dffd..0000000 --- a/blog/categories/haskell/index.html +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - - - Category: Haskell | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -

2014

- - - - - - - - -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/blog/categories/lisp/atom.xml b/blog/categories/lisp/atom.xml deleted file mode 100644 index 0325140..0000000 --- a/blog/categories/lisp/atom.xml +++ /dev/null @@ -1,153 +0,0 @@ - - - - <![CDATA[Category: Lisp | Stack Unwinding]]> - - - 2014-02-02T18:42:05-05:00 - http://chrislambda.github.io/ - - - - - Octopress - - - - <![CDATA[From Coding to Calculating]]> - - 2014-01-20T17:18:10-05:00 - http://chrislambda.github.io/blog/2014/01/20/from-coding-to-calculating - Like many aspiring software developers and computer scientists, I wanted to improve my art by going through Structure and Interpretation of Computer Programs. I fully expected to emerge from it all with Sherlock Holmes-esque abilities in programming. Yeah, that was about 7 years ago. Still, every so often, I’ll do some of the exercises and search online to see how my answers compared to others’.

- - - -

Because I didn’t spend a ton of time on these problems, I usually only implemented a naive solution and could only marvel at others’ much cooler, more clever, more efficient solutions. But I could never be sure if they were equivalent. Or could I?

- -

Here’s the example that inspired me to write this post, from SICP:

- -
-

Exercise 2.28. Write a procedure fringe that takes as argument a tree (represented as a list) and returns a list whose elements are all the leaves of the tree arranged in left-to-right order. For example,

-
- -
(define x (list (list 1 2) (list 3 4)))
-
-(fringe x)
-;; equals (1 2 3 4)
-
-(fringe (list x x))
-;; equals (1 2 3 4 1 2 3 4)
-
- -

If you’re not a Scheme user, this function takes something that looks like ((1 2) (3 4)) and gives something that looks like (1 2 3 4). It “gets the numbers out of the parentheses”. The nifty thing about this function is that it will do this for arbitrarily-nested trees, not just one level deep.

- -

This function also shows up in Paul Graham’s On Lisp, where it’s called flatten. In Let Over Lambda, Doug Hoyte points out that since Lisp code is represented by Lisp trees, flatten performs code-walking.

- -

How do you solve a problem like this? One way is to reason about the kinds of values your tree/list can be, and what your function should do with the tree/list when it assumes those values. In Scheme, a list can represent a binary tree. If tr is a tree, then (car tr) gives you the left branch and (cdr tr) gives you the right branch. So thinking recursively, let’s say someone’s already flattened the left and right branch. What now? Well, just append the flattened-left branch to the flattened-right branch and you’re done.

- -

I regard trees as being empty, being a leaf, or being a branch with two leaves. We covered the latter case just above, so let’s handle the other two:

- -
    -
  • -

    Empty, represented by the empty list (). What does a flattened empty list look like? That’s right: just the empty list, so return that.

    -
  • -
  • -

    A leaf. We want the function to return a list though, so return a list containing this value (i.e., wrap it in parentheses).

    -
  • -
- -

That’s all three cases! Here’s a naive solution in Common Lisp:

- -
(defun flatten (tree)
-	(cond ((null tree) nil)
-	      ((atom tree) (cons tree nil))
-	      (t (append (flatten (car tree))
-		         (flatten (cdr tree))))))
-
- -

Lisp code, so beautiful! Here, null tests for the empty list and atom tests to see if the tree is a leaf.

- -

This is all well and good, and it satisfies the test cases from SICP. But if you search for other answers to this question, or even look at the definition in On Lisp, there’s one that looks a bit different:

- -
(defun flatten (tree)
-   (labels ((rec (x acc)
-      (cond ((null x) acc)
-                ((atom x) (cons x acc))
-                (t (rec (car x) (rec (cdr x) acc))))))
-    (rec tree nil)))
-
- -

Here, labels introduces a local helper function in Common Lisp called rec that takes an accumulator. Except for this, it appears to be of the same “form” as the naive solution. It’s not immediately obvious to me how this second solution is equivalent to the first one. Even though I understood the logic of the second solution, I don’t think I would have come up with it on my own, even though I’m familiar with the accumulator technique.

- -

I wanted to not have to be clever. Even more, I wanted to be able to prove the equivalence of any other version of this function. What I wanted was to derive these equivalences in the same way I proved theorems in geometry or performed manipulations in algebra.

- -

Enter Haskell.

- -

I had been reading about Haskell here and there, and I appreciated that you could reason about programs, even calculate them. This is the method described in the book Algebra of Programming, which uses the Gofer language, an ancestor of Haskell. So I’ll recast this in Haskell because its syntax is ultra-clean. First, let’s roll out our own tree data type:

- -
data Tree a = Empty | Leaf a | Branch (Tree a) (Tree a)
-
- -

A tree can be either empty, a leaf holding a value of type a, or a branch of two trees, just like we said above. The flatten function translates into Haskell easily:

- -
flatten :: Tree a -> [a]
-flatten Empty = []
-flatten (Leaf a) = [a]
-flatten (Branch l r) = flatten l ++ flatten r
-
- -

Here, [a] means a list with one value in it, and ++ means “append”. The declaration at the top tells us this function takes a Tree holding values of type a and returns a list. Here’s an example of the function in action:

- -
flatten (Branch (Branch (Leaf 2) (Branch (Branch (Leaf 11) (Empty)) (Leaf 7))) (Leaf 4))
-[2,11,7,4]
-
- -

To make this look more like the second solution, let’s use the accumulator technique, the benefits of which I’ll go over in another post. So we define a helper function, flat_helper, that takes an accumulator as its second value:

- -
flat_helper :: Tree a -> [a] -> [a]
-flat_helper tree list = flatten tree ++ list
-
- -

We flatten tree, and then push the result onto list. I chose this form to “accumulate” the result into list. We can achieve our final result as follows:

- -
flat_helper tree [] = flatten tree ++ [] = flatten tree
-
- -

What does flat_helper do to the tree? The great thing about this is that we can just mechanically derive what flat_helper does from its definition:

- -
flat_helper Empty lst =
-  { definition of flat_helper }
-flatten Empty ++ lst =
-  { definition of flatten }
-[] ++ lst =
-lst
-
-flat_helper (Leaf a) lst =
-  { definition of flat_helper }
-flatten (Leaf a) ++ lst =
-  { definition of flatten }
-[a] ++ lst
-
-flat_helper (Branch l r) lst = 
-  { definition of flat_helper }
-flatten (Branch l r) ++ lst =
-  { definition of flatten }
-(flatten l ++ flatten r) ++ lst =
-  { associativity of append }
-flatten l ++ (flatten r ++ lst) =
-  { definition of flat_helper }
-flatten l ++ flat_helper r lst = 
-  { definition of flat_helper }
-flat_helper l (flat_helper r lst)
-
- -

Et voila! This is the same code from the second solution written in Haskell. I derived one solution from the other in a way that’s almost mathematical. It turns out that it’s more efficient than the naive solution, and we don’t even need to use ++. I wish I could learn from programs like this all the time.

- -

I based the above derivation from a chapter in Introduction to Functional Programming in Haskell, which has several more derivations like the above (I used their bracket comment notation). They called it synthesizing the program, which is a good way to think about it.

- -

I started out using computers to do work I would find tedious or unfeasible to do on my own–iterating over millions of variables, or printing a report every hour. Now I can use them to help me think about computers. A lot of fun for one problem in SICP, I’d say!

- -]]>
-
- -
diff --git a/blog/categories/lisp/index.html b/blog/categories/lisp/index.html deleted file mode 100644 index f503a31..0000000 --- a/blog/categories/lisp/index.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - Category: Lisp | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -

2014

- - - -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/blog/categories/scheme/atom.xml b/blog/categories/scheme/atom.xml deleted file mode 100644 index 97e3ddc..0000000 --- a/blog/categories/scheme/atom.xml +++ /dev/null @@ -1,153 +0,0 @@ - - - - <![CDATA[Category: Scheme | Stack Unwinding]]> - - - 2014-02-02T18:42:05-05:00 - http://chrislambda.github.io/ - - - - - Octopress - - - - <![CDATA[From Coding to Calculating]]> - - 2014-01-20T17:18:10-05:00 - http://chrislambda.github.io/blog/2014/01/20/from-coding-to-calculating - Like many aspiring software developers and computer scientists, I wanted to improve my art by going through Structure and Interpretation of Computer Programs. I fully expected to emerge from it all with Sherlock Holmes-esque abilities in programming. Yeah, that was about 7 years ago. Still, every so often, I’ll do some of the exercises and search online to see how my answers compared to others’.

- - - -

Because I didn’t spend a ton of time on these problems, I usually only implemented a naive solution and could only marvel at others’ much cooler, more clever, more efficient solutions. But I could never be sure if they were equivalent. Or could I?

- -

Here’s the example that inspired me to write this post, from SICP:

- -
-

Exercise 2.28. Write a procedure fringe that takes as argument a tree (represented as a list) and returns a list whose elements are all the leaves of the tree arranged in left-to-right order. For example,

-
- -
(define x (list (list 1 2) (list 3 4)))
-
-(fringe x)
-;; equals (1 2 3 4)
-
-(fringe (list x x))
-;; equals (1 2 3 4 1 2 3 4)
-
- -

If you’re not a Scheme user, this function takes something that looks like ((1 2) (3 4)) and gives something that looks like (1 2 3 4). It “gets the numbers out of the parentheses”. The nifty thing about this function is that it will do this for arbitrarily-nested trees, not just one level deep.

- -

This function also shows up in Paul Graham’s On Lisp, where it’s called flatten. In Let Over Lambda, Doug Hoyte points out that since Lisp code is represented by Lisp trees, flatten performs code-walking.

- -

How do you solve a problem like this? One way is to reason about the kinds of values your tree/list can be, and what your function should do with the tree/list when it assumes those values. In Scheme, a list can represent a binary tree. If tr is a tree, then (car tr) gives you the left branch and (cdr tr) gives you the right branch. So thinking recursively, let’s say someone’s already flattened the left and right branch. What now? Well, just append the flattened-left branch to the flattened-right branch and you’re done.

- -

I regard trees as being empty, being a leaf, or being a branch with two leaves. We covered the latter case just above, so let’s handle the other two:

- -
    -
  • -

    Empty, represented by the empty list (). What does a flattened empty list look like? That’s right: just the empty list, so return that.

    -
  • -
  • -

    A leaf. We want the function to return a list though, so return a list containing this value (i.e., wrap it in parentheses).

    -
  • -
- -

That’s all three cases! Here’s a naive solution in Common Lisp:

- -
(defun flatten (tree)
-	(cond ((null tree) nil)
-	      ((atom tree) (cons tree nil))
-	      (t (append (flatten (car tree))
-		         (flatten (cdr tree))))))
-
- -

Lisp code, so beautiful! Here, null tests for the empty list and atom tests to see if the tree is a leaf.

- -

This is all well and good, and it satisfies the test cases from SICP. But if you search for other answers to this question, or even look at the definition in On Lisp, there’s one that looks a bit different:

- -
(defun flatten (tree)
-   (labels ((rec (x acc)
-      (cond ((null x) acc)
-                ((atom x) (cons x acc))
-                (t (rec (car x) (rec (cdr x) acc))))))
-    (rec tree nil)))
-
- -

Here, labels introduces a local helper function in Common Lisp called rec that takes an accumulator. Except for this, it appears to be of the same “form” as the naive solution. It’s not immediately obvious to me how this second solution is equivalent to the first one. Even though I understood the logic of the second solution, I don’t think I would have come up with it on my own, even though I’m familiar with the accumulator technique.

- -

I wanted to not have to be clever. Even more, I wanted to be able to prove the equivalence of any other version of this function. What I wanted was to derive these equivalences in the same way I proved theorems in geometry or performed manipulations in algebra.

- -

Enter Haskell.

- -

I had been reading about Haskell here and there, and I appreciated that you could reason about programs, even calculate them. This is the method described in the book Algebra of Programming, which uses the Gofer language, an ancestor of Haskell. So I’ll recast this in Haskell because its syntax is ultra-clean. First, let’s roll out our own tree data type:

- -
data Tree a = Empty | Leaf a | Branch (Tree a) (Tree a)
-
- -

A tree can be either empty, a leaf holding a value of type a, or a branch of two trees, just like we said above. The flatten function translates into Haskell easily:

- -
flatten :: Tree a -> [a]
-flatten Empty = []
-flatten (Leaf a) = [a]
-flatten (Branch l r) = flatten l ++ flatten r
-
- -

Here, [a] means a list with one value in it, and ++ means “append”. The declaration at the top tells us this function takes a Tree holding values of type a and returns a list. Here’s an example of the function in action:

- -
flatten (Branch (Branch (Leaf 2) (Branch (Branch (Leaf 11) (Empty)) (Leaf 7))) (Leaf 4))
-[2,11,7,4]
-
- -

To make this look more like the second solution, let’s use the accumulator technique, the benefits of which I’ll go over in another post. So we define a helper function, flat_helper, that takes an accumulator as its second value:

- -
flat_helper :: Tree a -> [a] -> [a]
-flat_helper tree list = flatten tree ++ list
-
- -

We flatten tree, and then push the result onto list. I chose this form to “accumulate” the result into list. We can achieve our final result as follows:

- -
flat_helper tree [] = flatten tree ++ [] = flatten tree
-
- -

What does flat_helper do to the tree? The great thing about this is that we can just mechanically derive what flat_helper does from its definition:

- -
flat_helper Empty lst =
-  { definition of flat_helper }
-flatten Empty ++ lst =
-  { definition of flatten }
-[] ++ lst =
-lst
-
-flat_helper (Leaf a) lst =
-  { definition of flat_helper }
-flatten (Leaf a) ++ lst =
-  { definition of flatten }
-[a] ++ lst
-
-flat_helper (Branch l r) lst = 
-  { definition of flat_helper }
-flatten (Branch l r) ++ lst =
-  { definition of flatten }
-(flatten l ++ flatten r) ++ lst =
-  { associativity of append }
-flatten l ++ (flatten r ++ lst) =
-  { definition of flat_helper }
-flatten l ++ flat_helper r lst = 
-  { definition of flat_helper }
-flat_helper l (flat_helper r lst)
-
- -

Et voila! This is the same code from the second solution written in Haskell. I derived one solution from the other in a way that’s almost mathematical. It turns out that it’s more efficient than the naive solution, and we don’t even need to use ++. I wish I could learn from programs like this all the time.

- -

I based the above derivation from a chapter in Introduction to Functional Programming in Haskell, which has several more derivations like the above (I used their bracket comment notation). They called it synthesizing the program, which is a good way to think about it.

- -

I started out using computers to do work I would find tedious or unfeasible to do on my own–iterating over millions of variables, or printing a report every hour. Now I can use them to help me think about computers. A lot of fun for one problem in SICP, I’d say!

- -]]>
-
- -
diff --git a/blog/categories/scheme/index.html b/blog/categories/scheme/index.html deleted file mode 100644 index 0ca7198..0000000 --- a/blog/categories/scheme/index.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - Category: Scheme | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -

2014

- - - -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/blog/categories/sicp/atom.xml b/blog/categories/sicp/atom.xml deleted file mode 100644 index 45b02af..0000000 --- a/blog/categories/sicp/atom.xml +++ /dev/null @@ -1,153 +0,0 @@ - - - - <![CDATA[Category: sicp | Stack Unwinding]]> - - - 2014-02-02T18:42:05-05:00 - http://chrislambda.github.io/ - - - - - Octopress - - - - <![CDATA[From Coding to Calculating]]> - - 2014-01-20T17:18:10-05:00 - http://chrislambda.github.io/blog/2014/01/20/from-coding-to-calculating - Like many aspiring software developers and computer scientists, I wanted to improve my art by going through Structure and Interpretation of Computer Programs. I fully expected to emerge from it all with Sherlock Holmes-esque abilities in programming. Yeah, that was about 7 years ago. Still, every so often, I’ll do some of the exercises and search online to see how my answers compared to others’.

- - - -

Because I didn’t spend a ton of time on these problems, I usually only implemented a naive solution and could only marvel at others’ much cooler, more clever, more efficient solutions. But I could never be sure if they were equivalent. Or could I?

- -

Here’s the example that inspired me to write this post, from SICP:

- -
-

Exercise 2.28. Write a procedure fringe that takes as argument a tree (represented as a list) and returns a list whose elements are all the leaves of the tree arranged in left-to-right order. For example,

-
- -
(define x (list (list 1 2) (list 3 4)))
-
-(fringe x)
-;; equals (1 2 3 4)
-
-(fringe (list x x))
-;; equals (1 2 3 4 1 2 3 4)
-
- -

If you’re not a Scheme user, this function takes something that looks like ((1 2) (3 4)) and gives something that looks like (1 2 3 4). It “gets the numbers out of the parentheses”. The nifty thing about this function is that it will do this for arbitrarily-nested trees, not just one level deep.

- -

This function also shows up in Paul Graham’s On Lisp, where it’s called flatten. In Let Over Lambda, Doug Hoyte points out that since Lisp code is represented by Lisp trees, flatten performs code-walking.

- -

How do you solve a problem like this? One way is to reason about the kinds of values your tree/list can be, and what your function should do with the tree/list when it assumes those values. In Scheme, a list can represent a binary tree. If tr is a tree, then (car tr) gives you the left branch and (cdr tr) gives you the right branch. So thinking recursively, let’s say someone’s already flattened the left and right branch. What now? Well, just append the flattened-left branch to the flattened-right branch and you’re done.

- -

I regard trees as being empty, being a leaf, or being a branch with two leaves. We covered the latter case just above, so let’s handle the other two:

- -
    -
  • -

    Empty, represented by the empty list (). What does a flattened empty list look like? That’s right: just the empty list, so return that.

    -
  • -
  • -

    A leaf. We want the function to return a list though, so return a list containing this value (i.e., wrap it in parentheses).

    -
  • -
- -

That’s all three cases! Here’s a naive solution in Common Lisp:

- -
(defun flatten (tree)
-	(cond ((null tree) nil)
-	      ((atom tree) (cons tree nil))
-	      (t (append (flatten (car tree))
-		         (flatten (cdr tree))))))
-
- -

Lisp code, so beautiful! Here, null tests for the empty list and atom tests to see if the tree is a leaf.

- -

This is all well and good, and it satisfies the test cases from SICP. But if you search for other answers to this question, or even look at the definition in On Lisp, there’s one that looks a bit different:

- -
(defun flatten (tree)
-   (labels ((rec (x acc)
-      (cond ((null x) acc)
-                ((atom x) (cons x acc))
-                (t (rec (car x) (rec (cdr x) acc))))))
-    (rec tree nil)))
-
- -

Here, labels introduces a local helper function in Common Lisp called rec that takes an accumulator. Except for this, it appears to be of the same “form” as the naive solution. It’s not immediately obvious to me how this second solution is equivalent to the first one. Even though I understood the logic of the second solution, I don’t think I would have come up with it on my own, even though I’m familiar with the accumulator technique.

- -

I wanted to not have to be clever. Even more, I wanted to be able to prove the equivalence of any other version of this function. What I wanted was to derive these equivalences in the same way I proved theorems in geometry or performed manipulations in algebra.

- -

Enter Haskell.

- -

I had been reading about Haskell here and there, and I appreciated that you could reason about programs, even calculate them. This is the method described in the book Algebra of Programming, which uses the Gofer language, an ancestor of Haskell. So I’ll recast this in Haskell because its syntax is ultra-clean. First, let’s roll out our own tree data type:

- -
data Tree a = Empty | Leaf a | Branch (Tree a) (Tree a)
-
- -

A tree can be either empty, a leaf holding a value of type a, or a branch of two trees, just like we said above. The flatten function translates into Haskell easily:

- -
flatten :: Tree a -> [a]
-flatten Empty = []
-flatten (Leaf a) = [a]
-flatten (Branch l r) = flatten l ++ flatten r
-
- -

Here, [a] means a list with one value in it, and ++ means “append”. The declaration at the top tells us this function takes a Tree holding values of type a and returns a list. Here’s an example of the function in action:

- -
flatten (Branch (Branch (Leaf 2) (Branch (Branch (Leaf 11) (Empty)) (Leaf 7))) (Leaf 4))
-[2,11,7,4]
-
- -

To make this look more like the second solution, let’s use the accumulator technique, the benefits of which I’ll go over in another post. So we define a helper function, flat_helper, that takes an accumulator as its second value:

- -
flat_helper :: Tree a -> [a] -> [a]
-flat_helper tree list = flatten tree ++ list
-
- -

We flatten tree, and then push the result onto list. I chose this form to “accumulate” the result into list. We can achieve our final result as follows:

- -
flat_helper tree [] = flatten tree ++ [] = flatten tree
-
- -

What does flat_helper do to the tree? The great thing about this is that we can just mechanically derive what flat_helper does from its definition:

- -
flat_helper Empty lst =
-  { definition of flat_helper }
-flatten Empty ++ lst =
-  { definition of flatten }
-[] ++ lst =
-lst
-
-flat_helper (Leaf a) lst =
-  { definition of flat_helper }
-flatten (Leaf a) ++ lst =
-  { definition of flatten }
-[a] ++ lst
-
-flat_helper (Branch l r) lst = 
-  { definition of flat_helper }
-flatten (Branch l r) ++ lst =
-  { definition of flatten }
-(flatten l ++ flatten r) ++ lst =
-  { associativity of append }
-flatten l ++ (flatten r ++ lst) =
-  { definition of flat_helper }
-flatten l ++ flat_helper r lst = 
-  { definition of flat_helper }
-flat_helper l (flat_helper r lst)
-
- -

Et voila! This is the same code from the second solution written in Haskell. I derived one solution from the other in a way that’s almost mathematical. It turns out that it’s more efficient than the naive solution, and we don’t even need to use ++. I wish I could learn from programs like this all the time.

- -

I based the above derivation from a chapter in Introduction to Functional Programming in Haskell, which has several more derivations like the above (I used their bracket comment notation). They called it synthesizing the program, which is a good way to think about it.

- -

I started out using computers to do work I would find tedious or unfeasible to do on my own–iterating over millions of variables, or printing a report every hour. Now I can use them to help me think about computers. A lot of fun for one problem in SICP, I’d say!

- -]]>
-
- -
diff --git a/blog/categories/sicp/index.html b/blog/categories/sicp/index.html deleted file mode 100644 index b372ab7..0000000 --- a/blog/categories/sicp/index.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - Category: sicp | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -

2014

- - - -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/blog/index.html b/blog/index.html deleted file mode 100644 index f76e111..0000000 --- a/blog/index.html +++ /dev/null @@ -1,249 +0,0 @@ - - - - - - - - From physics to Big Data, functional programming, and more. | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-

- - - - Catamorphisms in 15 Minutes! -

-
-

Let’s say you have a list of numbers. What can you do with it? Find its length, map a function over it, add them up, etc. Wouldn’t it be great if there were one function to rule them all, a function that generalizes what you can do with a list? In many languages, there is a higher-order function called fold that encapsulates a pattern of structural recursion over recursive structures: to specify a function on a list, you specify two exhaustive cases: what to do to an empty list, and what to do with any other list. A generalization of this generalization is the notion of a catamorphism, which is what I’m writing about today.

- - - - More! → -
- - -
-
- - - - - - - - - - - -
- - - - - - - Comments - -
- - -
-

- - - - From Coding to Calculating -

-
-

Like many aspiring software developers and computer scientists, I wanted to improve my art by going through Structure and Interpretation of Computer Programs. I fully expected to emerge from it all with Sherlock Holmes-esque abilities in programming. Yeah, that was about 7 years ago. Still, every so often, I’ll do some of the exercises and search online to see how my answers compared to others’.

- - - - More! → -
- - -
-
- - - - - - - - - - - -
- - - - - - - Comments - -
- -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/config.rb b/config.rb new file mode 100644 index 0000000..408aeca --- /dev/null +++ b/config.rb @@ -0,0 +1,19 @@ +require 'sass-globbing' + +# Require any additional compass plugins here. +project_type = :stand_alone + +# Publishing paths +http_path = "/" +http_images_path = "/images" +http_generated_images_path = "/images" +http_fonts_path = "/fonts" +css_dir = "public/stylesheets" + +# Local development paths +sass_dir = "sass" +images_dir = "source/images" +fonts_dir = "source/fonts" + +line_comments = false +output_style = :compressed diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..8e3dc08 --- /dev/null +++ b/config.ru @@ -0,0 +1,25 @@ +require 'bundler/setup' +require 'sinatra/base' + +# The project root directory +$root = ::File.dirname(__FILE__) + +class SinatraStaticServer < Sinatra::Base + + get(/.+/) do + send_sinatra_file(request.path) {404} + end + + not_found do + send_file(File.join(File.dirname(__FILE__), 'public', '404.html'), {:status => 404}) + end + + def send_sinatra_file(path, &missing_file_block) + file_path = File.join(File.dirname(__FILE__), 'public', path) + file_path = File.join(file_path, 'index.html') unless file_path =~ /\.[a-z]+$/i + File.exist?(file_path) ? send_file(file_path) : missing_file_block.call + end + +end + +run SinatraStaticServer diff --git a/favicon.png b/favicon.png deleted file mode 100644 index a4d840d..0000000 Binary files a/favicon.png and /dev/null differ diff --git a/font/fontawesome-webfont.eot b/font/fontawesome-webfont.eot deleted file mode 100755 index 3f669a7..0000000 Binary files a/font/fontawesome-webfont.eot and /dev/null differ diff --git a/font/fontawesome-webfont.svg b/font/fontawesome-webfont.svg deleted file mode 100755 index 73c0ad9..0000000 --- a/font/fontawesome-webfont.svg +++ /dev/null @@ -1,175 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Designer : Dave Gandy -Foundry : Fort Awesome - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/font/fontawesome-webfont.svgz b/font/fontawesome-webfont.svgz deleted file mode 100755 index 2a73cd7..0000000 Binary files a/font/fontawesome-webfont.svgz and /dev/null differ diff --git a/font/fontawesome-webfont.ttf b/font/fontawesome-webfont.ttf deleted file mode 100755 index 4972eb4..0000000 Binary files a/font/fontawesome-webfont.ttf and /dev/null differ diff --git a/font/fontawesome-webfont.woff b/font/fontawesome-webfont.woff deleted file mode 100755 index 6e4cb41..0000000 Binary files a/font/fontawesome-webfont.woff and /dev/null differ diff --git a/images/dp.jpg b/images/dp.jpg deleted file mode 100644 index 6cb8dc6..0000000 Binary files a/images/dp.jpg and /dev/null differ diff --git a/images/fancybox/blank.gif b/images/fancybox/blank.gif deleted file mode 100644 index 35d42e8..0000000 Binary files a/images/fancybox/blank.gif and /dev/null differ diff --git a/images/fancybox/fancybox_loading.gif b/images/fancybox/fancybox_loading.gif deleted file mode 100644 index 0158617..0000000 Binary files a/images/fancybox/fancybox_loading.gif and /dev/null differ diff --git a/images/fancybox/fancybox_sprite.png b/images/fancybox/fancybox_sprite.png deleted file mode 100644 index a1aae1a..0000000 Binary files a/images/fancybox/fancybox_sprite.png and /dev/null differ diff --git a/images/loading_gray.gif b/images/loading_gray.gif deleted file mode 100644 index 945a2aa..0000000 Binary files a/images/loading_gray.gif and /dev/null differ diff --git a/images/loading_pacman.gif b/images/loading_pacman.gif deleted file mode 100644 index 8f61add..0000000 Binary files a/images/loading_pacman.gif and /dev/null differ diff --git a/images/social/coderwall.png b/images/social/coderwall.png deleted file mode 100644 index 48e32cf..0000000 Binary files a/images/social/coderwall.png and /dev/null differ diff --git a/images/social/delicious.png b/images/social/delicious.png deleted file mode 100644 index a936776..0000000 Binary files a/images/social/delicious.png and /dev/null differ diff --git a/images/social/facebook.png b/images/social/facebook.png deleted file mode 100644 index 2e43bd8..0000000 Binary files a/images/social/facebook.png and /dev/null differ diff --git a/images/social/flickr.png b/images/social/flickr.png deleted file mode 100644 index 1cb1f3e..0000000 Binary files a/images/social/flickr.png and /dev/null differ diff --git a/images/social/github.png b/images/social/github.png deleted file mode 100644 index 6962c9d..0000000 Binary files a/images/social/github.png and /dev/null differ diff --git a/images/social/google.png b/images/social/google.png deleted file mode 100644 index 68f6978..0000000 Binary files a/images/social/google.png and /dev/null differ diff --git a/images/social/linkedin.png b/images/social/linkedin.png deleted file mode 100644 index 1a7ae91..0000000 Binary files a/images/social/linkedin.png and /dev/null differ diff --git a/images/social/pinboard.png b/images/social/pinboard.png deleted file mode 100644 index 2961f30..0000000 Binary files a/images/social/pinboard.png and /dev/null differ diff --git a/images/social/pinterest.png b/images/social/pinterest.png deleted file mode 100644 index 34a64d6..0000000 Binary files a/images/social/pinterest.png and /dev/null differ diff --git a/images/social/rss.png b/images/social/rss.png deleted file mode 100644 index 8492267..0000000 Binary files a/images/social/rss.png and /dev/null differ diff --git a/images/social/twitter.png b/images/social/twitter.png deleted file mode 100644 index 1d63f2d..0000000 Binary files a/images/social/twitter.png and /dev/null differ diff --git a/index.html b/index.html deleted file mode 100644 index d475b74..0000000 --- a/index.html +++ /dev/null @@ -1,249 +0,0 @@ - - - - - - - - From physics to Big Data, functional programming, and more. | Stack Unwinding - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-

- - - - Catamorphisms in 15 Minutes! -

-
-

Let’s say you have a list of numbers. What can you do with it? Find its length, map a function over it, add them up, etc. Wouldn’t it be great if there were one function to rule them all, a function that generalizes what you can do with a list? In many languages, there is a higher-order function called fold that encapsulates a pattern of structural recursion over recursive structures: to specify a function on a list, you specify two exhaustive cases: what to do to an empty list, and what to do with any other list. A generalization of this generalization is the notion of a catamorphism, which is what I’m writing about today.

- - - - More! → -
- - -
-
- - - - - - - - - - - -
- - - - - - - Comments - -
- - -
-

- - - - From Coding to Calculating -

-
-

Like many aspiring software developers and computer scientists, I wanted to improve my art by going through Structure and Interpretation of Computer Programs. I fully expected to emerge from it all with Sherlock Holmes-esque abilities in programming. Yeah, that was about 7 years ago. Still, every so often, I’ll do some of the exercises and search online to see how my answers compared to others’.

- - - - More! → -
- - -
-
- - - - - - - - - - - -
- - - - - - - Comments - -
- -
-
Copyright © 2014 - - Chris Jones - -
-Powered by Octopress. -
- - - - - - - - - - - - - diff --git a/javascripts/ender.js b/javascripts/ender.js deleted file mode 100644 index 1add349..0000000 --- a/javascripts/ender.js +++ /dev/null @@ -1,45 +0,0 @@ -/*! - * ============================================================= - * Ender: open module JavaScript framework (https://ender.no.de) - * Build: ender build jeesh reqwest - * ============================================================= - */ - - -/*! - * Ender: open module JavaScript framework (client-lib) - * copyright Dustin Diaz & Jacob Thornton 2011 (@ded @fat) - * http://ender.no.de - * License MIT - */ -!function(a){function d(a){var c=b[a]||window[a];if(!c)throw new Error("Requested module '"+a+"' has not been defined.");return c}function e(a,c){return b[a]=c}function f(a,b){for(var c in b)c!="noConflict"&&c!="_VERSION"&&(a[c]=b[c]);return a}function g(a,b,c){return h._select&&(typeof a=="string"||a.nodeName||a.length&&"item"in a||a==window)?(c=h._select(a,b),c.selector=a):c=isFinite(a.length)?a:[a],f(c,g)}function h(a,b){return g(a,b)}a.global=a;var b={},c=a.$;a.provide=e,a.require=d,f(h,{_VERSION:"0.3.4",fn:a.$&&a.$.fn||{},ender:function(a,b){f(b?g:h,a)},_select:function(a,b){return(b||document).querySelectorAll(a)}}),f(g,{forEach:function(a,b,c){for(c=0,l=this.length;c0?this:[]).concat(Array.prototype.slice.call(arguments,0));return b[a].apply(null,c)}},d=c("serialize"),e=c("serializeArray");a.ender({ajax:b,serialize:d,serializeArray:e,toQueryString:b.toQueryString}),a.ender({serialize:d,serializeArray:e},!0)}(ender)}(),!function(){var a={exports:{}},b=a.exports; -/*! - * bean.js - copyright Jacob Thornton 2011 - * https://github.com/fat/bean - * MIT License - * special thanks to: - * dean edwards: http://dean.edwards.name/ - * dperini: https://github.com/dperini/nwevents - * the entire mootools team: github.com/mootools/mootools-core - */ -!function(b,c){typeof define=="function"?define(c):typeof a!="undefined"?a.exports=c():this[b]=c()}("bean",function(){function F(a){var b=a.relatedTarget;return b?b!=this&&b.prefix!="xul"&&!/document/.test(this.toString())&&!p(this,b):b===null}var a=window,b=1,c={},d={},e=/over|out/,f=/[^\.]*(?=\..*)\.|.*/,g=/\..*/,h="addEventListener",i="attachEvent",j="removeEventListener",k="detachEvent",l=document||{},m=l.documentElement||{},n=m[h],o=n?h:i,p=function(a,b){var c=b.parentNode;while(c!==null){if(c==a)return!0;c=c.parentNode}},q=function(a,c){return a.__uid=c&&c+"::"+b++||a.__uid||b++},r=function(a){var b=q(a);return c[b]=c[b]||{}},s=n?function(a,b,c,d){a[d?h:j](b,c,!1)}:function(a,b,c,d,e){e&&d&&a["_on"+e]===null&&(a["_on"+e]=0),a[d?i:k]("on"+b,c)},t=function(b,c,d){return function(e){return e=D(e||((this.ownerDocument||this.document||this).parentWindow||a).event),c.apply(b,[e].concat(d))}},u=function(b,c,d,e,f){return function(g){if(e?e.apply(this,arguments):n?!0:g&&g.propertyName=="_on"+d||!g)g=g?D(g||((this.ownerDocument||this.document||this).parentWindow||a).event):null,c.apply(b,Array.prototype.slice.call(arguments,g?0:1).concat(f))}},v=function(a,b,c,e){var h=b.replace(g,""),i=r(a),j=i[h]||(i[h]={}),k=c,l=q(c,b.replace(f,""));if(j[l])return a;var m=G[h];m&&(c=m.condition?u(a,c,h,m.condition):c,h=m.base||h);var p=E[h];c=p?t(a,c,e):u(a,c,h,!1,e),p=n||p;if(h=="unload"){var v=c;c=function(){w(a,h,c)&&v()}}return a[o]&&s(a,p?h:"propertychange",c,!0,!p&&h),j[l]=c,c.__uid=l,c.__originalFn=k,h=="unload"?a:d[q(a)]=a},w=function(a,b,c){function l(b){c=j[k][b];if(!c)return;delete j[k][b];if(a[o]){k=G[k]?G[k].base:k;var d=n||E[k];s(a,d?k:"propertychange",c,!1,!d&&k)}}var d,e,h,i,j=r(a),k=b.replace(g,"");if(!j||!j[k])return a;e=b.replace(f,""),h=e?e.split("."):[c.__uid],l(e);for(i=h.length;i--;l(h[i]));return a},x=function(a,b,c){return function(d){var e=typeof a=="string"?c(a,this):a;for(var f=d.target;f&&f!=this;f=f.parentNode)for(var g=e.length;g--;)if(e[g]==f)return b.apply(f,arguments)}},y=function(a,b,c,d,e){if(typeof b=="object"&&!c)for(var f in b)b.hasOwnProperty(f)&&y(a,f,b[f]);else{var g=typeof c=="string",h=(g?c:b).split(" ");c=g?x(b,d,e):c;for(var i=h.length;i--;)v(a,h[i],c,Array.prototype.slice.call(arguments,g?4:3))}return a},z=function(a,b,c){var d,e,h,i,j,k=typeof b=="string",l=k&&b.replace(f,""),m=w,n=r(a);l=l&&l.split(".");if(k&&/\s/.test(b)){b=b.split(" "),j=b.length-1;while(z(a,b[j])&&j--);return a}i=k?b.replace(g,""):b;if(!n||l||k&&!n[i]){for(d in n)if(n.hasOwnProperty(d))for(j in n[d])for(e=l.length;e--;)n[d].hasOwnProperty(j)&&(new RegExp("^"+l[e]+"::\\d*(\\..*)?$")).test(j)&&m(a,[d,j].join("."));return a}if(typeof c=="function")m(a,i,c);else if(l)m(a,b);else{m=i?m:z,h=k&&i,i=i?c||n[i]||i:n;for(d in i)i.hasOwnProperty(d)&&(m(a,h||d,i[d]),delete i[d])}return a},A=function(a,b,c){var d,e,h,i,j=b.split(" ");for(h=j.length;h--;){b=j[h].replace(g,"");var k=E[b],l=j[h].replace(f,""),m=r(a)[b];if(l){l=l.split(".");for(e=l.length;e--;)for(i in m)m.hasOwnProperty(i)&&(new RegExp("^"+l[e]+"::\\d*(\\..*)?$")).test(i)&&m[i].apply(a,[!1].concat(c))}else if(!c&&a[o])B(k,b,a);else for(e in m)m.hasOwnProperty(e)&&m[e].apply(a,[!1].concat(c))}return a},B=n?function(b,c,d){evt=document.createEvent(b?"HTMLEvents":"UIEvents"),evt[b?"initEvent":"initUIEvent"](c,!0,!0,a,1),d.dispatchEvent(evt)}:function(a,b,c){a?c.fireEvent("on"+b,document.createEventObject()):c["_on"+b]++},C=function(a,b,c){var d=r(b),e,f,g=q(a);e=c?d[c]:d;for(f in e)e.hasOwnProperty(f)&&(c?y:C)(a,c||b,c?e[f].__originalFn:f);return a},D=function(a){var b={};if(!a)return b;var c=a.type,d=a.target||a.srcElement;b.preventDefault=D.preventDefault(a),b.stopPropagation=D.stopPropagation(a),b.target=d&&d.nodeType==3?d.parentNode:d;if(~c.indexOf("key"))b.keyCode=a.which||a.keyCode;else if(/click|mouse|menu/i.test(c)){b.rightClick=a.which==3||a.button==2,b.pos={x:0,y:0};if(a.pageX||a.pageY)b.clientX=a.pageX,b.clientY=a.pageY;else if(a.clientX||a.clientY)b.clientX=a.clientX+document.body.scrollLeft+document.documentElement.scrollLeft,b.clientY=a.clientY+document.body.scrollTop+document.documentElement.scrollTop;e.test(c)&&(b.relatedTarget=a.relatedTarget||a[(c=="mouseover"?"from":"to")+"Element"])}for(var f in a)f in b||(b[f]=a[f]);return b};D.preventDefault=function(a){return function(){a.preventDefault?a.preventDefault():a.returnValue=!1}},D.stopPropagation=function(a){return function(){a.stopPropagation?a.stopPropagation():a.cancelBubble=!0}};var E={click:1,dblclick:1,mouseup:1,mousedown:1,contextmenu:1,mousewheel:1,DOMMouseScroll:1,mouseover:1,mouseout:1,mousemove:1,selectstart:1,selectend:1,keydown:1,keypress:1,keyup:1,orientationchange:1,touchstart:1,touchmove:1,touchend:1,touchcancel:1,gesturestart:1,gesturechange:1,gestureend:1,focus:1,blur:1,change:1,reset:1,select:1,submit:1,load:1,unload:1,beforeunload:1,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,error:1,abort:1,scroll:1},G={mouseenter:{base:"mouseover",condition:F},mouseleave:{base:"mouseout",condition:F},mousewheel:{base:/Firefox/.test(navigator.userAgent)?"DOMMouseScroll":"mousewheel"}},H={add:y,remove:z,clone:C,fire:A},I=function(a){var b=z(a).__uid;b&&(delete d[b],delete c[b])};return a[i]&&y(a,"unload",function(){for(var b in d)d.hasOwnProperty(b)&&I(d[b]);a.CollectGarbage&&CollectGarbage()}),H.noConflict=function(){return context.bean=old,this},H}),provide("bean",a.exports),!function(a){var b=require("bean"),c=function(c,d,e){var f=d?[d]:[];return function(){for(var e,g=0,h=this.length;g]+)/.exec(a),d=c.createElement(b&&k[b[1].toLowerCase()]||"div"),e=[];d.innerHTML=a;var f=d.childNodes;d=d.firstChild,e.push(d);while(d=d.nextSibling)d.nodeType==1&&e.push(d);return e}():A(a)?[a.cloneNode(!0)]:[]},N.doc=function(){var a=this.viewport();return{width:Math.max(c.body.scrollWidth,d.scrollWidth,a.width),height:Math.max(c.body.scrollHeight,d.scrollHeight,a.height)}},N.firstChild=function(a){for(var b=a.childNodes,c=0,d=b&&b.length||0,e;c])\s*/g,C=/[\s\>\+\~]/,D=/(?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\]|[\s\w\+\-]*\))/,E=/([.*+?\^=!:${}()|\[\]\/\\])/g,F=/^([a-z0-9]+)?(?:([\.\#]+[\w\-\.#]+)?)/,G=/\[([\w\-]+)(?:([\|\^\$\*\~]?\=)['"]?([ \w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^]+)["']?)?\]/,H=/:([\w\-]+)(\(['"]?([\s\w\+\-]+)['"]?\))?/,I=new RegExp("("+C.source+")"+D.source,"g"),J=new RegExp(C.source+D.source),K=new RegExp(F.source+"("+G.source+")?"+"("+H.source+")?"),L={" ":function(a){return a&&a!==u&&a.parentNode},">":function(a,b){return a&&a.parentNode==b.parentNode&&a.parentNode},"~":function(a){return a&&a.previousSibling},"+":function(a,b,c,d){return a?(c=S(a),d=S(b),c&&d&&c==d&&c):!1}};M.prototype={g:function(a){return this.c[a]||undefined},s:function(a,b){return this.c[a]=b,b}};var N=new M,O=new M,P=new M,Q=new M,bb="compareDocumentPosition"in u?function(a,b){return(b.compareDocumentPosition(a)&16)==16}:"contains"in u?function(a,c){return c=c==b||c==window?u:c,c!==a&&c.contains(a)}:function(a,b){while(a=a.parentNode)if(a===b)return 1;return 0},bc=function(){if(!b.querySelector||!b.querySelectorAll)return!1;try{return b.querySelectorAll(":nth-of-type(1)").length}catch(a){return!1}}(),bd=bc?function(a,c){return b.getElementsByClassName&&(i=a.match(y))?R(c.getElementsByClassName(i[1])):R(c.querySelectorAll(a))}:function(a,c){a=a.replace(B,"$1");var d=[],e,g,j=[],k;if(i=a.match(A)){s=c.getElementsByTagName(i[1]||"*"),l=N.g(i[2])||N.s(i[2],new RegExp("(^|\\s+)"+i[2]+"(\\s+|$)"));for(k=0,h=s.length,f=0;k]+)\s*/.exec(b)[1],f=(c||a).createElement(e[d]||"div"),g=[];f.innerHTML=b;var h=f.childNodes;f=f.firstChild,f.nodeType==1&&g.push(f);while(f=f.nextSibling)f.nodeType==1&&g.push(f);return g}var c=require("qwery"),d="table",e={thead:d,tbody:d,tfoot:d,tr:"tbody",th:"tr",td:"tr",fieldset:"form",option:"select"};b._select=function(a,b){return/^\s*. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -MathJax.Hub.Register.StartupHook("End Extensions",function () { - - var FP = MathJax.Extension.fp = { - version: "0.1" - }; - - /************ Matcher **************/ - FP.Matcher = MathJax.Object.Subclass({ - Init: function () { this.cases = []; }, - Case: function (klass, f) { - this.cases.push([klass, f]); - return this; - }, - match: function (x) { - if (x instanceof Object && "isa" in x) { - var i, count, klass, op; - i = 0; - count = this.cases.length; - while (i < count) { - klass = this.cases[i][0]; - if (x.isa(klass)) { - op = klass.unapply(x); - if (op.isDefined) { - return this.cases[i][1](op.get); - } - } - i = i + 1; - } - } - throw FP.MatchError(x); - } - }); - - /************ Option **************/ - FP.Option = MathJax.Object.Subclass({}); - - FP.Option.Some = FP.Option.Subclass({ - Init: function (value) { - this.get = value; - }, - isEmpty: false, - isDefined: true, - getOrElse: function (ignore) { return this.get; }, - flatMap: function (k) { - return k(this.get); - }, - map: function (f) { - return FP.Option.Some(f(this.get)); - }, - foreach: function (f) { - f(this.get); - }, - toString: function () { - return "Some(" + this.get + ")"; - } - }, { - unapply: function (x) { return FP.Option.Some(x.get); } - }); - - FP.Option.None = FP.Option.Subclass({ - Init: function () {}, - isEmpty: true, - isDefined: false, - getOrElse: function (value) { return value; }, - flatMap: function (k) { return this; }, - foreach: function (f) {}, - map: function (k) { return this; }, - toString: function () { return "None"; } - }, { - unapply: function (x) { return FP.Option.Some(x); } - }); - - FP.Option.Augment({}, { - empty: FP.Option.None() - }); - - - /************ List **************/ - FP.List = MathJax.Object.Subclass({}); - - FP.List.Cons = FP.List.Subclass({ - Init: function (head, tail) { - this.head = head; - this.tail = tail; - }, - isEmpty: false, - at: function (index) { - if (index < 0 || index >= this.length()) { - throw Error("no such element at " + index + ". index must be lower than " + this.length() + "."); - } - var t = this; - for (var i = 0; i < index; i++) { - t = t.tail; - } - return t.head; - }, - length: function () { - var t = this; - var l = 0; - while (!t.isEmpty) { - l++; - t = t.tail; - } - return l; - }, - prepend: function (element) { - return FP.List.Cons(element, this); - }, - append: function (element) { - var result = FP.List.Cons(element, FP.List.empty); - this.reverse().foreach(function (e) { - result = FP.List.Cons(e, result); - }); - return result; - }, - concat: function (that) { - var result = that; - this.reverse().foreach(function (e) { - result = FP.List.Cons(e, result); - }); - return result; - }, - foldLeft: function (x0, f) { - var r, c; - r = f(x0, this.head); - c = this.tail; - while (!c.isEmpty) { - r = f(r, c.head); - c = c.tail; - } - return r; - }, - foldRight: function (x0, f) { - if (this.tail.isEmpty) { - return f(this.head, x0); - } else { - return f(this.head, this.tail.foldRight(x0, f)); - } - }, - map: function (f) { - return FP.List.Cons(f(this.head), this.tail.map(f)); - }, - flatMap: function (k) { - return k(this.head).concat(this.tail.flatMap(k)); - }, - foreach: function (f) { - var e = this; - while (!e.isEmpty) { - f(e.head); - e = e.tail; - } - }, - reverse: function () { - var r = FP.List.empty; - this.foreach(function (c) { - r = FP.List.Cons(c, r); - }); - return r; - }, - mkString: function () { - var open, delim, close; - switch (arguments.length) { - case 0: - open = delim = close = ""; - break; - case 1: - delim = arguments[0]; - open = close = ""; - break; - case 2: - open = arguments[0]; - delim = arguments[1]; - close = ""; - break; - default: - open = arguments[0]; - delim = arguments[1]; - close = arguments[2]; - break; - } - var desc, nxt; - desc = open + this.head.toString(); - nxt = this.tail; - while (nxt.isa(FP.List.Cons)) { - desc += delim + nxt.head.toString(); - nxt = nxt.tail; - } - desc += close; - return desc; - }, - toString: function () { - return this.mkString("[", ", ", "]"); - } - }, { - unapply: function (x) { return FP.Option.Some([x.head, x.tail]); } - }); - - FP.List.Nil = FP.List.Subclass({ - isEmpty: true, - at: function (index) { - throw Error("cannot get element from an empty list."); - }, - length: function () { return 0; }, - prepend: function (element) { - return FP.List.Cons(element, FP.List.empty); - }, - append: function (element) { - return FP.List.Cons(element, FP.List.empty); - }, - concat: function (that) { - return that; - }, - foldLeft: function (x0, f) { return x0; }, - foldRight: function (x0, f) { return x0; }, - flatMap: function (f) { return this; }, - map: function (f) { return this; }, - foreach: function (f) {}, - reverse: function () { return this; }, - mkString: function () { - switch (arguments.length) { - case 0: - case 1: - return ""; - case 2: - return arguments[0] - default: - return arguments[0]+arguments[2]; - } - }, - toString: function () { return '[]'; } - }, { - unapply: function (x) { return FP.Option.Some(x); } - }); - - FP.List.Augment({}, { - empty: FP.List.Nil(), - fromArray: function (as) { - var list, i; - list = FP.List.empty; - i = as.length - 1; - while (i >= 0) { - list = FP.List.Cons(as[i], list); - i -= 1; - } - return list; - } - }); - - - /************ MatchError **************/ - FP.MatchError = MathJax.Object.Subclass({ - Init: function (obj) { this.obj = obj; }, - // getMessage: function () { - // if (this.obj === null) { - // return "null" - // } else { - // return obj.toString() + " (of class " + obj. + ")" - // } - // } - toString: function () { return "MatchError(" + this.obj + ")"; } - }); - - - /************ OffsetPosition **************/ - FP.OffsetPosition = MathJax.Object.Subclass({ - Init: function (source, offset) { - // assert(source.length >= offset) - this.source = source; - if (offset === undefined) { this.offset = 0; } else { this.offset = offset; } - this._index = null; - this._line = null; - }, - index: function () { - if (this._index !== null) { return this._index; } - this._index = []; - this._index.push(0); - var i = 0; - while (i < this.source.length) { - if (this.source.charAt(i) === '\n') { this._index.push(i + 1); } - i += 1; - } - this._index.push(this.source.length); - return this._index; - }, - line: function () { - var lo, hi, mid; - if (this._line !== null) { return this._line; } - lo = 0; - hi = this.index().length - 1; - while (lo + 1 < hi) { - mid = (hi + lo) >> 1; - if (this.offset < this.index()[mid]) { - hi = mid; - } else { - lo = mid; - } - } - this._line = lo + 1; - return this._line; - }, - column: function () { - return this.offset - this.index()[this.line() - 1] + 1; - }, - lineContents: function () { - var i, l; - i = this.index(); - l = this.line(); - return this.source.substring(i[l - 1], i[l]); - }, - toString: function () { return this.line().toString() + '.' + this.column(); }, - longString: function () { - var desc, i; - desc = this.lineContents() + '\n'; - i = 0; - while (i < this.column()) { - if (this.lineContents().charAt(i) === '\t') { - desc += '\t'; - } else { - desc += ' '; - } - i += 1; - } - desc += '^'; - return desc; - }, - isLessThan: function (that) { - if (that.isa(FP.OffsetPosition)) { - return this.offset < that.offset; - } else { - return ( - this.line() < that.line() || - (this.line() === that.line() && this.column() < that.column()) - ); - } - } - }); - - - /************ StringReader **************/ - FP.StringReader = MathJax.Object.Subclass({ - Init: function (source, offset, context) { - this.source = source; - this.offset = offset; - this.context = context; - }, - first: function () { - if (this.offset < this.source.length) { - return this.source.charAt(this.offset); - } else { - return FP.StringReader.EofCh; - } - }, - rest: function () { - if (this.offset < this.source.length) { - return FP.StringReader(this.source, this.offset + 1, this.context); - } else { - return this; - } - }, - pos: function () { return FP.OffsetPosition(this.source, this.offset); }, - atEnd: function () { return this.offset >= this.source.length; }, - drop: function (n) { - var r, count; - r = this; - count = n; - while (count > 0) { - r = r.rest(); - count -= 1; - } - return r; - } - }, { - EofCh: '\x03' - }); - - - /************ Parsers **************/ - FP.Parsers = MathJax.Object.Subclass({}, { - parse: function (p, input) { - return p.apply(input); - }, - parseAll: function (p, input) { - return p.andl(function () { return FP.Parsers.eos(); }).apply(input); - }, - parseString: function (p, str) { - var input = FP.StringReader(str, 0, { lastNoSuccess: undefined }); - return FP.Parsers.parse(p, input); - }, - parseAllString: function (p, str) { - var input = FP.StringReader(str, 0, { lastNoSuccess: undefined }); - return FP.Parsers.parseAll(p, input); - }, - _handleWhiteSpace: function (input) { - var whiteSpaceRegex = input.context.whiteSpaceRegex; - var source = input.source; - var offset = input.offset; - var m = whiteSpaceRegex.exec(source.substring(offset, source.length)); - if (m !== null) { - return offset + m[0].length; - } else { - return offset; - } - }, - literal: function (str) { - return FP.Parsers.Parser(function (input) { - var source, offset, start, i, j, found; - source = input.source; - offset = input.offset; - start = FP.Parsers._handleWhiteSpace(input); - i = 0; - j = start; - while (i < str.length && j < source.length && - str.charAt(i) === source.charAt(j)) { - i += 1; - j += 1; - } - if (i === str.length) { - return FP.Parsers.Success(str, input.drop(j - offset)); - } else { - if (start === source.length) { - found = "end of source"; - } else { - found = "`" + source.charAt(start) + "'"; - } - return FP.Parsers.Failure( - "`" + str + "' expected but " + found + " found", - input.drop(start - offset) - ); - } - }); - }, - regex: function (rx /* must start with ^ */) { - if (rx.toString().substring(0, 2) !== "/^") { - throw ("regex must start with `^' but " + rx); - } - return FP.Parsers.Parser(function (input) { - var source, offset, m, found; - source = input.source; - offset = input.offset; - m = rx.exec(source.substring(offset, source.length)); - if (m !== null) { - return FP.Parsers.Success(m[0], input.drop(m[0].length)); - } else { - if (offset === source.length) { - found = "end of source"; - } else { - found = "`" + source.charAt(offset) + "'"; - } - return FP.Parsers.Failure( - "string matching regex " + rx + " expected but " + found + " found", - input - ); - } - }); - }, - regexLiteral: function (rx /* must start with ^ */) { - if (rx.toString().substring(0, 2) !== "/^") { - throw ("regex must start with `^' but " + rx); - } - return FP.Parsers.Parser(function (input) { - var source, offset, start, m, found; - source = input.source; - offset = input.offset; - start = FP.Parsers._handleWhiteSpace(input); - m = rx.exec(source.substring(start, source.length)); - if (m !== null) { - return FP.Parsers.Success(m[0], input.drop(start + m[0].length - offset)); - } else { - if (start === source.length) { - found = "end of source"; - } else { - found = "`" + source.charAt(start) + "'"; - } - return FP.Parsers.Failure( - "string matching regex " + rx + " expected but " + found + " found", - input.drop(start - offset) - ); - } - }); - }, - eos: function () { - return FP.Parsers.Parser(function (input) { - var source, offset, start; - source = input.source; - offset = input.offset; - start = FP.Parsers._handleWhiteSpace(input); - if (source.length === start) { - return FP.Parsers.Success("", input); - } else { - return FP.Parsers.Failure("end of source expected but `" + - source.charAt(start) + "' found", input); - } - }); - }, - commit: function (/*lazy*/ p) { - return FP.Parsers.Parser(function (input) { - var res = p()(input); - return (FP.Matcher() - .Case(FP.Parsers.Success, function (x) { return res; }) - .Case(FP.Parsers.Error, function (x) { return res; }) - .Case(FP.Parsers.Failure, function (x) { - return FP.Parsers.Error(x[0], x[1]); - }).match(res) - ); - }); - }, - //elem: function (kind, p) - elem: function (e) { return FP.Parsers.accept(e).named('"' + e + '"'); }, - accept: function (e) { - return FP.Parsers.acceptIf( - function (x) { return x === e; }, - function (x) { return "`" + e + "' expected but `" + x + "' found"; } - ); - }, - acceptIf: function (p, err) { - return FP.Parsers.Parser(function (input) { - if (p(input.first())) { - return FP.Parsers.Success(input.first(), input.rest()); - } else { - return FP.Parsers.Failure(err(input.first()), input); - } - }); - }, - //acceptMatch: function (expected, f) - //acceptSeq: function (es) - failure: function (msg) { - return FP.Parsers.Parser(function (input) { - return FP.Parsers.Failure(msg, input); - }); - }, - err: function (msg) { - return FP.Parsers.Parser(function (input) { - return FP.Parsers.Error(msg, input); - }); - }, - success: function (v) { - return FP.Parsers.Parser(function (input) { - return FP.Parsers.Success(v, input); - }); - }, - log: function (/*lazy*/ p, name) { - return FP.Parsers.Parser(function (input) { - console.log("trying " + name + " at " + input); - var r = p().apply(input); - console.log(name + " --> " + r); - return r; - }); - }, - rep: function (/*lazy*/ p) { - var s = FP.Parsers.success(FP.List.empty); - return FP.Parsers.rep1(p).or(function () { return s; }); - }, - rep1: function (/*lazy*/ p) { - return FP.Parsers.Parser(function (input) { - var elems, i, p0, res; - elems = []; - i = input; - p0 = p(); - res = p0.apply(input); - if (res.isa(FP.Parsers.Success)) { - while (res.isa(FP.Parsers.Success)) { - elems.push(res.result); - i = res.next; - res = p0.apply(i); - } - return FP.Parsers.Success(FP.List.fromArray(elems), i); - } else { - return res; - } - }); - }, - //rep1: function (/*lazy*/ first, /*lazy*/ p) - repN: function (num, /*lazy*/ p) { - if (num === 0) { - return FP.Parsers.success(FP.List.empty); - } - return FP.Parsers.Parser(function (input) { - var elems, i, p0, res; - elems = []; - i = input; - p0 = p(); - res = p0.apply(i); - while (res.isa(FP.Parsers.Success)) { - elems.push(res.result); - i = res.next; - if (num === elems.length) { - return FP.Parsers.Success(FP.List.fromArray(elems), i); - } - res = p0.apply(i); - } - return res; // NoSuccess - }); - }, - repsep: function (/*lazy*/ p, /*lazy*/ q) { - var s = FP.Parsers.success(FP.List.empty); - return FP.Parsers.rep1sep(p, q).or(function () { return s; }); - }, - rep1sep: function (/*lazy*/ p, /*lazy*/ q) { - return p().and(FP.Parsers.rep(q().andr(p))).to(function (res) { - return FP.List.Cons(res.head, res.tail); - }); - }, - // chainl1: function (/*lazy*/ p, /*lazy*/ q) { - // return this.chainl1(p, p, q) - // }, - chainl1: function (/*lazy*/ first, /*lazy*/ p, /*lazy*/ q) { - return first().and(FP.Parsers.rep(q().and(p))).to(function (res) { - return res.tail.foldLeft(res.head, function (a, fb) { return fb.head(a, fb.tail); }); - }); - }, - chainr1: function (/*lazy*/ p, /*lazy*/ q, combine, first) { - return p().and(this.rep(q().and(p))).to(function (res) { - return FP.List.Cons(FP.Parsers.Pair(combine, res.head), - res.tail).foldRight(first, function (fa, b) { return fa.head(fa.tail, b); } - ); - }); - }, - opt: function (/*lazy*/ p) { - return p().to(function (x) { - return FP.Option.Some(x); - }).or(function () { - return FP.Parsers.success(FP.Option.empty); - }); - }, - not: function (/*lazy*/ p) { - return FP.Parsers.Parser(function (input) { - var r = p().apply(input); - if (r.successful) { - return FP.Parsers.Failure("Expected failure", input); - } else { - return FP.Parsers.Success(FP.Option.empty, input); - } - }); - }, - guard: function (/*lazy*/ p) { - return FP.Parsers.Parser(function (input) { - var r = p().apply(input); - if (r.successful) { - return FP.Parsers.Success(r.result, input); - } else { - return r; - } - }); - }, - //positioned: function (/*lazy*/ p) - //phrase: function (p) - mkList: function (pair) { return FP.List.Cons(pair.head, pair.tail); }, - fun: function (x) { return function () { return x; }; }, - lazyParser: function (x) { - var lit, r; - if (x instanceof String || (typeof x) === "string") { - lit = FP.Parsers.literal(x); - return function () { return lit; }; - } else if (x instanceof Function) { - // x is deemed to be a function which has the return value as Parser. - return x; - } else if (x instanceof Object) { - if("isa" in x && x.isa(FP.Parsers.Parser)) { - return function () { return x; }; - } else if (x instanceof RegExp) { - r = FP.Parsers.regexLiteral(x); - return function () { return r; }; - } else { - return FP.Parsers.err("unhandlable type"); - } - } else { - return FP.Parsers.err("unhandlable type"); - } - }, - seq: function () { - var count, parser, i; - count = arguments.length; - if (count === 0) { return FP.Parsers.err("at least one element must be specified"); } - parser = FP.Parsers.lazyParser(arguments[0])(); - i = 1; - while (i < count) { - parser = parser.and(FP.Parsers.lazyParser(arguments[i])); - i += 1; - } - return parser; - }, - or: function () { - var count, parser, i; - count = arguments.length; - if (count === 0) { return FP.Parsers.err("at least one element must be specified"); } - parser = FP.Parsers.lazyParser(arguments[0])(); - i = 1; - while (i < count) { - parser = parser.or(FP.Parsers.lazyParser(arguments[i])); - i += 1; - } - return parser; - } - }); - - - /************ Pair **************/ - FP.Parsers.Pair = MathJax.Object.Subclass({ - Init: function (head, tail) { - this.head = head; - this.tail = tail; - }, - toString: function () { return '(' + this.head + '~' + this.tail + ')'; } - }, { - unapply: function (x) { return FP.Option.Some([x.head, x.tail]); } - }); - - - /************ ParseResult **************/ - FP.Parsers.ParseResult = MathJax.Object.Subclass({ - Init: function () {}, - isEmpty: function () { return !this.successful; }, - getOrElse: function (/*lazy*/ defaultValue) { - if (this.isEmpty) { return defaultValue(); } else { return this.get(); } - } - }); - - - /************ Success **************/ - FP.Parsers.Success = FP.Parsers.ParseResult.Subclass({ - Init: function (result, next) { - this.result = result; - this.next = next; - }, - map: function (f) { return FP.Parsers.Success(f(this.result), this.next); }, - mapPartial: function (f, err) { - try { - return FP.Parsers.Success(f(this.result), this.next); - } catch (e) { - if ("isa" in e && e.isa(FP.MatchError)) { - return FP.Parsers.Failure(err(this.result), this.next); - } else { - throw e; - } - } - }, - flatMapWithNext: function (f) { return f(this.result).apply(this.next); }, - append: function (/*lazy*/ a) { return this; }, - get: function () { return this.result; }, - successful: true, - toString: function () { return '[' + this.next.pos() + '] parsed: ' + this.result; } - }, { - unapply: function (x) { return FP.Option.Some([x.result, x.next]); } - }); - - - /************ NoSuccess **************/ - FP.Parsers.NoSuccess = FP.Parsers.ParseResult.Subclass({ - Init: function () {}, - _setLastNoSuccess: function () { - var context = this.next.context; - if (context.lastNoSuccess === undefined || !this.next.pos().isLessThan(context.lastNoSuccess.next.pos())) { - context.lastNoSuccess = this; - } - }, - map: function (f) { return this; }, - mapPartial: function (f, error) { return this; }, - flatMapWithNext: function (f) { return this; }, - get: function () { return FP.Parsers.error("No result when parsing failed"); }, - successful: false - }); - - - /************ Failure **************/ - FP.Parsers.Failure = FP.Parsers.NoSuccess.Subclass({ - Init: function (msg, next) { - this.msg = msg; - this.next = next; - this._setLastNoSuccess(); - }, - append: function (/*lazy*/ a) { - var alt = a(); - if (alt.isa(FP.Parsers.Success)) { - return alt; - } else if (alt.isa(FP.Parsers.NoSuccess)) { - if (alt.next.pos().isLessThan(this.next.pos())) { - return this; - } else { - return alt; - } - } else { - throw FP.MatchError(alt); - } - }, - toString: function () { return ('[' + this.next.pos() + '] failure: ' + - this.msg + '\n\n' + this.next.pos().longString()); } - }, { - unapply: function (x) { return FP.Option.Some([x.msg, x.next]); } - }); - - - /************ Error **************/ - FP.Parsers.Error = FP.Parsers.NoSuccess.Subclass({ - Init: function (msg, next) { - this.msg = msg; - this.next = next; - this._setLastNoSuccess(); - }, - append: function (/*lazy*/ a) { return this; }, - toString: function () { return ('[' + this.next.pos() + '] error: ' + - this.msg + '\n\n' + this.next.pos().longString()); } - }, { - unapply: function (x) { return FP.Option.Some([x.msg, x.next]); } - }); - - - /************ Parser **************/ - FP.Parsers.Parser = MathJax.Object.Subclass({ - Init: function (f) { this.apply = f; }, - name: '', - named: function (name) { this.name = name; return this; }, - toString: function () { return 'Parser (' + this.name + ')'; }, - flatMap: function (f) { - var app = this.apply; - return FP.Parsers.Parser(function (input) { - return app(input).flatMapWithNext(f); - }); - }, - map: function (f) { - var app = this.apply; - return FP.Parsers.Parser(function (input) { - return app(input).map(f); - }); - }, - append: function (/*lazy*/ p) { - var app = this.apply; - return FP.Parsers.Parser(function (input) { - return app(input).append(function () { - return p().apply(input); - }); - }); - }, - and: function (/*lazy*/ p) { - return this.flatMap(function (a) { - return p().map(function (b) { - return FP.Parsers.Pair(a, b); - }); - }).named('~'); - }, - andr: function (/*lazy*/ p) { - return this.flatMap(function (a) { - return p().map(function (b) { - return b; - }); - }).named('~>'); - }, - andl: function (/*lazy*/ p) { - return this.flatMap(function (a) { - return p().map(function (b) { - return a; - }); - }).named('<~'); - }, - or: function (/*lazy*/ q) { return this.append(q).named("|"); }, - andOnce: function (/*lazy*/ p) { - var flatMap = this.flatMap; - return FP.Parsers.OnceParser(function () { - return flatMap(function (a) { - return FP.Parsers.commit(p).map(function (b) { - return FP.Parsers.Pair(a, b); - }); - }).named('~!'); - }); - }, - longestOr: function (/*lazy*/ q0) { - var app = this.apply; - return FP.Parsers.Parser(function (input) { - var res1, res2; - res1 = app(input); - res2 = q0()(input); - if (res1.successful) { - if (res2.successful) { - if (res2.next.pos().isLessThan(res1.next.pos())) { - return res1; - } else { - return res2; - } - } else { - return res1; - } - } else if (res2.successful) { - return res2; - } else if (res1.isa(FP.Parsers.Error)) { - return res1; - } else { - if (res2.next.pos().isLessThan(res1.next.pos())) { - return res1; - } else { - return res2; - } - } - }).named("|||"); - }, - to: function (f) { return this.map(f).named(this.toString() + '^^'); }, - ret: function (/*lazy*/ v) { - var app = this.apply; - return FP.Parsers.Parser(function (input) { - return app(input).map(function (x) { return v(); }); - }).named(this.toString() + "^^^"); - }, - toIfPossible: function (f, error) { - if (error === undefined) { - error = function (r) { return "Constructor function not defined at " + r; }; - } - var app = this.apply; - return FP.Parsers.Parser(function (input) { - return app(input).mapPartial(f, error); - }).named(this.toString() + "^?"); - }, - into: function (fq) { return this.flatMap(fq); }, - rep: function () { - var p = this; - return FP.Parsers.rep(function () { return p; }); - }, - chain: function (/*lazy*/ sep) { - var p, lp; - p = this; - lp = function () { return p; }; - return FP.Parsers.chainl1(lp, lp, sep); - }, - rep1: function () { - var p = this; - return FP.Parsers.rep1(function () { return p; }); - }, - opt: function () { - var p = this; - return FP.Parsers.opt(function () { return p; }); - } - }); - - - /************ OnceParser **************/ - FP.Parsers.OnceParser = FP.Parsers.Parser.Subclass({ - Init: function (f) { this.apply = f; }, - and: function (p) { - var flatMap = this.flatMap; - return FP.Parsers.OnceParser(function () { - return flatMap(function (a) { - return FP.Parsers.commit(p).map(function (b) { - return FP.Parsers.Pair(a, b); - }); - }); - }).named('~'); - } - }); - - MathJax.Hub.Startup.signal.Post("Functional Programming library Ready"); -}); - -MathJax.Ajax.loadComplete("[MathJax]/extensions/fp.js"); \ No newline at end of file diff --git a/javascripts/jquery.fancybox.pack.js b/javascripts/jquery.fancybox.pack.js deleted file mode 100644 index 6521c21..0000000 --- a/javascripts/jquery.fancybox.pack.js +++ /dev/null @@ -1,35 +0,0 @@ -/*! fancyBox v2.0.6 fancyapps.com | fancyapps.com/fancybox/#license */ -(function(s,l,d,t){var m=d(s),q=d(l),a=d.fancybox=function(){a.open.apply(this,arguments)},u=!1,k=l.createTouch!==t,o=function(a){return"string"===d.type(a)},n=function(b,c){c&&o(b)&&0
',image:'',iframe:'",swf:'',error:'

The requested content cannot be loaded.
Please try again later.

', -closeBtn:'
',next:'',prev:''},openEffect:"fade",openSpeed:300,openEasing:"swing",openOpacity:!0,openMethod:"zoomIn",closeEffect:"fade",closeSpeed:300,closeEasing:"swing",closeOpacity:!0,closeMethod:"zoomOut",nextEffect:"elastic",nextSpeed:300,nextEasing:"swing",nextMethod:"changeIn",prevEffect:"elastic", -prevSpeed:300,prevEasing:"swing",prevMethod:"changeOut",helpers:{overlay:{speedIn:0,speedOut:300,opacity:0.8,css:{cursor:"pointer"},closeClick:!0},title:{type:"float"}}},group:{},opts:{},coming:null,current:null,isOpen:!1,isOpened:!1,player:{timer:null,isActive:!1},ajaxLoad:null,imgPreload:null,transitions:{},helpers:{},open:function(b,c){a.close(!0);b&&!d.isArray(b)&&(b=b instanceof d?d(b).get():[b]);a.isActive=!0;a.opts=d.extend(!0,{},a.defaults,c);d.isPlainObject(c)&&c.keys!==t&&(a.opts.keys=c.keys? -d.extend({},a.defaults.keys,c.keys):!1);a.group=b;a._start(a.opts.index||0)},cancel:function(){a.coming&&!1===a.trigger("onCancel")||(a.coming=null,a.hideLoading(),a.ajaxLoad&&a.ajaxLoad.abort(),a.ajaxLoad=null,a.imgPreload&&(a.imgPreload.onload=a.imgPreload.onabort=a.imgPreload.onerror=null))},close:function(b){a.cancel();a.current&&!1!==a.trigger("beforeClose")&&(a.unbindEvents(),!a.isOpen||b&&!0===b[0]?(d(".fancybox-wrap").stop().trigger("onReset").remove(),a._afterZoomOut()):(a.isOpen=a.isOpened= -!1,d(".fancybox-item, .fancybox-nav").remove(),a.wrap.stop(!0).removeClass("fancybox-opened"),a.inner.css("overflow","hidden"),a.transitions[a.current.closeMethod]()))},play:function(b){var c=function(){clearTimeout(a.player.timer)},e=function(){c();a.current&&a.player.isActive&&(a.player.timer=setTimeout(a.next,a.current.playSpeed))},f=function(){c();d("body").unbind(".player");a.player.isActive=!1;a.trigger("onPlayEnd")};if(a.player.isActive||b&&!1===b[0])f();else if(a.current&&(a.current.loop|| -a.current.index=a.group.length?b=0:0>b&&(b=a.group.length-1)),a.group[b]!==t&&(a.cancel(),a._start(b)))},reposition:function(b, -c){var e;a.isOpen&&(e=a._getPosition(c),b&&"scroll"===b.type?(delete e.position,a.wrap.stop(!0,!0).animate(e,200)):a.wrap.css(e))},update:function(b){a.isOpen&&(u||setTimeout(function(){var c=a.current,e=!b||b&&"orientationchange"===b.type;if(u&&(u=!1,c)){if(!b||"scroll"!==b.type||e)c.autoSize&&"iframe"!==c.type&&(a.inner.height("auto"),c.height=a.inner.height()),(c.autoResize||e)&&a._setDimension(),c.canGrow&&"iframe"!==c.type&&a.inner.height("auto");(c.autoCenter||e)&&a.reposition(b);a.trigger("onUpdate")}}, -200),u=!0)},toggle:function(){a.isOpen&&(a.current.fitToView=!a.current.fitToView,a.update())},hideLoading:function(){q.unbind("keypress.fb");d("#fancybox-loading").remove()},showLoading:function(){a.hideLoading();q.bind("keypress.fb",function(b){27===b.keyCode&&(b.preventDefault(),a.cancel())});d('
').click(a.cancel).appendTo("body")},getViewport:function(){return{x:m.scrollLeft(),y:m.scrollTop(),w:k&&s.innerWidth?s.innerWidth:m.width(),h:k&&s.innerHeight? -s.innerHeight:m.height()}},unbindEvents:function(){a.wrap&&a.wrap.unbind(".fb");q.unbind(".fb");m.unbind(".fb")},bindEvents:function(){var b=a.current,c=b.keys;b&&(m.bind("resize.fb orientationchange.fb"+(b.autoCenter&&!b.fixed?" scroll.fb":""),a.update),c&&q.bind("keydown.fb",function(b){var f;f=b.target||b.srcElement;if(!b.ctrlKey&&!b.altKey&&!b.shiftKey&&!b.metaKey&&(!f||!f.type&&!d(f).is("[contenteditable]")))f=b.keyCode,-1b.length))for(i=1;i<=h;i+=1)if(f=b[(c.index+i)%e],g=f.href||d(f).attr("href")||f,"image"===f.type||a.isImage(g))(new Image).src=g},_afterLoad:function(){a.hideLoading(); -!a.coming||!1===a.trigger("afterLoad",a.current)?a.coming=!1:(a.isOpened?(d(".fancybox-item, .fancybox-nav").remove(),a.wrap.stop(!0).removeClass("fancybox-opened"),a.inner.css("overflow","hidden"),a.transitions[a.current.prevMethod]()):(d(".fancybox-wrap").stop().trigger("onReset").remove(),a.trigger("afterClose")),a.unbindEvents(),a.isOpen=!1,a.current=a.coming,a.wrap=d(a.current.tpl.wrap).addClass("fancybox-"+(k?"mobile":"desktop")+" fancybox-type-"+a.current.type+" fancybox-tmp "+a.current.wrapCSS).appendTo("body"), -a.skin=d(".fancybox-skin",a.wrap).css("padding",n(a.current.padding)),a.outer=d(".fancybox-outer",a.wrap),a.inner=d(".fancybox-inner",a.wrap),a._setContent())},_setContent:function(){var b=a.current,c=b.content,e=b.type,f=b.minWidth,g=b.minHeight,i=b.maxWidth,h=b.maxHeight;switch(e){case "inline":case "ajax":case "html":b.selector?c=d("
").html(c).find(b.selector):c instanceof d&&(c.parent().hasClass("fancybox-inner")&&c.parents(".fancybox-wrap").unbind("onReset"),c=c.show().detach(),d(a.wrap).bind("onReset", -function(){c.appendTo("body").hide()}));b.autoSize&&(f=d('
').appendTo("body").css({minWidth:n(f,"w"),minHeight:n(g,"h"),maxWidth:n(i,"w"),maxHeight:n(h,"h")}).append(c),b.width=f.width(),b.height=f.height(),f.width(a.current.width),f.height()>b.height&&(f.width(b.width+1),b.width=f.width(),b.height=f.height()),c=f.contents().detach(),f.remove());break;case "image":c=b.tpl.image.replace("{href}",b.href);b.aspectRatio=!0;break;case "swf":c= -b.tpl.swf.replace(/\{width\}/g,b.width).replace(/\{height\}/g,b.height).replace(/\{href\}/g,b.href);break;case "iframe":c=d(b.tpl.iframe.replace("{rnd}",(new Date).getTime())).attr("scrolling",b.scrolling).attr("src",b.href),b.scrolling=k?"scroll":"auto"}if("image"===e||"swf"===e)b.autoSize=!1,b.scrolling="visible";"iframe"===e&&b.autoSize?(a.showLoading(),a._setDimension(),a.inner.css("overflow",b.scrolling),c.bind({onCancel:function(){d(this).unbind();a._afterZoomOut()},load:function(){a.hideLoading(); -try{this.contentWindow.document.location&&(a.current.height=d(this).contents().find("body").height())}catch(b){a.current.autoSize=!1}a[a.isOpen?"_afterZoomIn":"_beforeShow"]()}}).appendTo(a.inner)):(a.inner.append(c),a._beforeShow())},_beforeShow:function(){a.coming=null;a.trigger("beforeShow");a._setDimension();a.wrap.hide().removeClass("fancybox-tmp");a.bindEvents();a._preloadImages();a.transitions[a.isOpened?a.current.nextMethod:a.current.openMethod]()},_setDimension:function(){var b=a.wrap,c= -a.inner,e=a.current,f=a.getViewport(),g=e.margin,i=2*e.padding,h=e.width,j=e.height,r=e.maxWidth+i,k=e.maxHeight+i,l=e.minWidth+i,m=e.minHeight+i,p;f.w-=g[1]+g[3];f.h-=g[0]+g[2];o(h)&&0r&&(h=r,j=(h-i)/g+i),j>k&&(j=k,h=(j-i)*g+i),hr||p>k)for(;(h>r||p>k)&&h>l&&p>m;)j-=10,e.aspectRatio?(h=Math.round((j-i)*g+i),hm&&jf.w||p>f.h)&&h>l&&j>m)e.canShrink=!0;a.innerSpace=p-i- -c.height()},_getPosition:function(b){var c=a.current,e=a.getViewport(),f=c.margin,d=a.wrap.width()+f[1]+f[3],i=a.wrap.height()+f[0]+f[2],h={position:"absolute",top:f[0]+e.y,left:f[3]+e.x};c.autoCenter&&c.fixed&&!b&&i<=e.h&&d<=e.w&&(h={position:"fixed",top:f[0],left:f[3]});h.top=n(Math.max(h.top,h.top+(e.h-i)*c.topRatio));h.left=n(Math.max(h.left,h.left+0.5*(e.w-d)));return h},_afterZoomIn:function(){var b=a.current,c=b?b.scrolling:"no";if(b&&(a.isOpen=a.isOpened=!0,a.wrap.addClass("fancybox-opened"), -a.inner.css("overflow","yes"===c?"scroll":"no"===c?"hidden":c),a.trigger("afterShow"),a.update(),(b.closeClick||b.nextClick)&&a.inner.css("cursor","pointer").bind("click.fb",function(c){if(!d(c.target).is("a")&&!d(c.target).parent().is("a"))a[b.closeClick?"close":"next"]()}),b.closeBtn&&d(b.tpl.closeBtn).appendTo(a.skin).bind("click.fb",a.close),b.arrows&&1c.end&&(g=1-g),d-=a.innerSpace*g),a.inner[e](d)},zoomIn:function(){var b=a.wrap,c=a.current,e=c.openEffect,f="elastic"===e,g=d.extend({},c.dim, -a._getPosition(f)),i=d.extend({opacity:1},g);delete i.position;f?(g=this.getOrigPosition(),c.openOpacity&&(g.opacity=0),a.outer.add(a.inner).width("auto").height("auto")):"fade"===e&&(g.opacity=0);b.css(g).show().animate(i,{duration:"none"===e?0:c.openSpeed,easing:c.openEasing,step:f?this.step:null,complete:a._afterZoomIn})},zoomOut:function(){var b=a.wrap,c=a.current,d=c.openEffect,f="elastic"===d,g={opacity:0};f&&("fixed"===b.css("position")&&b.css(a._getPosition(!0)),g=this.getOrigPosition(),c.closeOpacity&& -(g.opacity=0));b.animate(g,{duration:"none"===d?0:c.closeSpeed,easing:c.closeEasing,step:f?this.step:null,complete:a._afterZoomOut})},changeIn:function(){var b=a.wrap,c=a.current,d=c.nextEffect,f="elastic"===d,g=a._getPosition(f),i={opacity:1};g.opacity=0;f&&(g.top=n(parseInt(g.top,10)-200),i.top="+=200px");b.css(g).show().animate(i,{duration:"none"===d?0:c.nextSpeed,easing:c.nextEasing,complete:a._afterZoomIn})},changeOut:function(){var b=a.wrap,c=a.current,e=c.prevEffect,f={opacity:0};b.removeClass("fancybox-opened"); -"elastic"===e&&(f.top="+=200px");b.animate(f,{duration:"none"===e?0:c.prevSpeed,easing:c.prevEasing,complete:function(){d(this).trigger("onReset").remove()}})}};a.helpers.overlay={overlay:null,update:function(){var a,c;this.overlay.width("100%").height("100%");d.browser.msie||k?(a=Math.max(l.documentElement.scrollWidth,l.body.scrollWidth),c=Math.max(l.documentElement.offsetWidth,l.body.offsetWidth),a=a
').css(b.css).appendTo("body"),b.closeClick&&this.overlay.bind("click.fb",a.close),a.current.fixed&&!k?this.overlay.addClass("overlay-fixed"):(this.update(),this.onUpdate=function(){this.update()}),this.overlay.fadeTo(b.speedIn,b.opacity))},afterClose:function(a){this.overlay&&this.overlay.fadeOut(a.speedOut||0,function(){d(this).remove()});this.overlay=null}};a.helpers.title={beforeShow:function(b){var c; -if(c=a.current.title)c=d('
'+c+"
").appendTo("body"),"float"===b.type&&(c.width(c.width()),c.wrapInner(''),a.current.margin[2]+=Math.abs(parseInt(c.css("margin-bottom"),10))),c.appendTo("over"===b.type?a.inner:"outside"===b.type?a.wrap:a.skin)}};d.fn.fancybox=function(b){var c=d(this),e=this.selector||"",f,g=function(g){var h=this,j=f,k;!g.ctrlKey&&!g.altKey&&!g.shiftKey&&!g.metaKey&&!d(h).is(".fancybox-wrap")&& -(g.preventDefault(),g=b.groupAttr||"data-fancybox-group",k=d(h).attr(g),k||(g="rel",k=h[g]),k&&""!==k&&"nofollow"!==k&&(h=e.length?d(e):c,h=h.filter("["+g+'="'+k+'"]'),j=h.index(this)),b.index=j,a.open(h,b))},b=b||{};f=b.index||0;e?q.undelegate(e,"click.fb-start").delegate(e,"click.fb-start",g):c.unbind("click.fb-start").bind("click.fb-start",g);return this};d(l).ready(function(){a.defaults.fixed=d.support.fixedPosition||!(d.browser.msie&&6>=d.browser.version)&&!k})})(window,document,jQuery); \ No newline at end of file diff --git a/javascripts/libs/ender.js b/javascripts/libs/ender.js deleted file mode 100644 index 5e56fd8..0000000 --- a/javascripts/libs/ender.js +++ /dev/null @@ -1,1497 +0,0 @@ -/*! - * Ender: open module JavaScript framework - * copyright Dustin Diaz & Jacob Thornton 2011 (@ded @fat) - * https://ender.no.de - * License MIT - * Build: ender -b jeesh - */ -!function (context) { - - function aug(o, o2) { - for (var k in o2) { - k != 'noConflict' && k != '_VERSION' && (o[k] = o2[k]); - } - return o; - } - - function boosh(s, r) { - var els; - if (ender._select && typeof s == 'string' || s.nodeName || s.length && 'item' in s || s == window) { //string || node || nodelist || window - els = ender._select(s, r); - els.selector = s; - } else { - els = isFinite(s.length) ? s : [s]; - } - return aug(els, boosh); - } - - function ender(s, r) { - return boosh(s, r); - } - - aug(ender, { - _VERSION: '0.2.0', - ender: function (o, chain) { - aug(chain ? boosh : ender, o); - } - }); - - aug(boosh, { - forEach: function (fn, scope) { - // opt out of native forEach so we can intentionally call our own scope - // defaulting to the current item - for (var i = 0, l = this.length; i < l; ++i) { - i in this && fn.call(scope || this[i], this[i], i, this); - } - // return self for chaining - return this; - } - }); - - var old = context.$; - ender.noConflict = function () { - context.$ = old; - return this; - }; - - (typeof module !== 'undefined') && module.exports && (module.exports = ender); - // use subscript notation as extern for Closure compilation - context['ender'] = context['$'] = ender; - -}(this); -/*! - * bean.js - copyright Jacob Thornton 2011 - * https://github.com/fat/bean - * MIT License - * special thanks to: - * dean edwards: http://dean.edwards.name/ - * dperini: https://github.com/dperini/nwevents - * the entire mootools team: github.com/mootools/mootools-core - */ -!function (context) { - var __uid = 1, registry = {}, collected = {}, - overOut = /over|out/, - namespace = /[^\.]*(?=\..*)\.|.*/, - stripName = /\..*/, - addEvent = 'addEventListener', - attachEvent = 'attachEvent', - removeEvent = 'removeEventListener', - detachEvent = 'detachEvent', - doc = context.document || {}, - root = doc.documentElement || {}, - W3C_MODEL = root[addEvent], - eventSupport = W3C_MODEL ? addEvent : attachEvent, - - isDescendant = function (parent, child) { - var node = child.parentNode; - while (node != null) { - if (node == parent) { - return true; - } - node = node.parentNode; - } - }, - - retrieveUid = function (obj, uid) { - return (obj.__uid = uid || obj.__uid || __uid++); - }, - - retrieveEvents = function (element) { - var uid = retrieveUid(element); - return (registry[uid] = registry[uid] || {}); - }, - - listener = W3C_MODEL ? function (element, type, fn, add) { - element[add ? addEvent : removeEvent](type, fn, false); - } : function (element, type, fn, add, custom) { - custom && add && (element['_on' + custom] = element['_on' + custom] || 0); - element[add ? attachEvent : detachEvent]('on' + type, fn); - }, - - nativeHandler = function (element, fn, args) { - return function (event) { - event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || context).event); - return fn.apply(element, [event].concat(args)); - }; - }, - - customHandler = function (element, fn, type, condition, args) { - return function (event) { - if (condition ? condition.call(this, event) : W3C_MODEL ? true : event && event.propertyName == '_on' + type || !event) { - fn.apply(element, [event].concat(args)); - } - }; - }, - - addListener = function (element, orgType, fn, args) { - var type = orgType.replace(stripName, ''), - events = retrieveEvents(element), - handlers = events[type] || (events[type] = {}), - uid = retrieveUid(fn, orgType.replace(namespace, '')); - if (handlers[uid]) { - return element; - } - var custom = customEvents[type]; - if (custom) { - fn = custom.condition ? customHandler(element, fn, type, custom.condition) : fn; - type = custom.base || type; - } - var isNative = nativeEvents[type]; - fn = isNative ? nativeHandler(element, fn, args) : customHandler(element, fn, type, false, args); - isNative = W3C_MODEL || isNative; - if (type == 'unload') { - var org = fn; - fn = function () { - removeListener(element, type, fn) && org(); - }; - } - element[eventSupport] && listener(element, isNative ? type : 'propertychange', fn, true, !isNative && type); - handlers[uid] = fn; - fn.__uid = uid; - return type == 'unload' ? element : (collected[retrieveUid(element)] = element); - }, - - removeListener = function (element, orgType, handler) { - var uid, names, uids, i, events = retrieveEvents(element), type = orgType.replace(stripName, ''); - if (!events || !events[type]) { - return element; - } - names = orgType.replace(namespace, ''); - uids = names ? names.split('.') : [handler.__uid]; - for (i = uids.length; i--;) { - uid = uids[i]; - handler = events[type][uid]; - delete events[type][uid]; - if (element[eventSupport]) { - type = customEvents[type] ? customEvents[type].base : type; - var isNative = W3C_MODEL || nativeEvents[type]; - listener(element, isNative ? type : 'propertychange', handler, false, !isNative && type); - } - } - return element; - }, - - del = function (selector, fn, $) { - return function (e) { - var array = typeof selector == 'string' ? $(selector, this) : selector; - for (var target = e.target; target && target != this; target = target.parentNode) { - for (var i = array.length; i--;) { - if (array[i] == target) { - return fn.apply(target, arguments); - } - } - } - }; - }, - - add = function (element, events, fn, delfn, $) { - if (typeof events == 'object' && !fn) { - for (var type in events) { - events.hasOwnProperty(type) && add(element, type, events[type]); - } - } else { - var isDel = typeof fn == 'string', types = (isDel ? fn : events).split(' '); - fn = isDel ? del(events, delfn, $) : fn; - for (var i = types.length; i--;) { - addListener(element, types[i], fn, Array.prototype.slice.call(arguments, isDel ? 4 : 3)); - } - } - return element; - }, - - remove = function (element, orgEvents, fn) { - var k, type, events, - isString = typeof(orgEvents) == 'string', - names = isString && orgEvents.replace(namespace, ''), - rm = removeListener, - attached = retrieveEvents(element); - if (isString && /\s/.test(orgEvents)) { - orgEvents = orgEvents.split(' '); - var i = orgEvents.length - 1; - while (remove(element, orgEvents[i]) && i--) {} - return element; - } - events = isString ? orgEvents.replace(stripName, '') : orgEvents; - if (!attached || (isString && !attached[events])) { - return element; - } - if (typeof fn == 'function') { - rm(element, events, fn); - } else if (names) { - rm(element, orgEvents); - } else { - rm = events ? rm : remove; - type = isString && events; - events = events ? (fn || attached[events] || events) : attached; - for (k in events) { - events.hasOwnProperty(k) && rm(element, type || k, events[k]); - } - } - return element; - }, - - fire = function (element, type, args) { - var evt, k, i, types = type.split(' '); - for (i = types.length; i--;) { - type = types[i].replace(stripName, ''); - var isNative = nativeEvents[type], - isNamespace = types[i].replace(namespace, ''), - handlers = retrieveEvents(element)[type]; - if (isNamespace) { - isNamespace = isNamespace.split('.'); - for (k = isNamespace.length; k--;) { - handlers[isNamespace[k]] && handlers[isNamespace[k]].apply(element, args); - } - } else if (!args && element[eventSupport]) { - fireListener(isNative, type, element); - } else { - for (k in handlers) { - handlers.hasOwnProperty(k) && handlers[k].apply(element, args); - } - } - } - return element; - }, - - fireListener = W3C_MODEL ? function (isNative, type, element) { - evt = document.createEvent(isNative ? "HTMLEvents" : "UIEvents"); - evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, context, 1); - element.dispatchEvent(evt); - } : function (isNative, type, element) { - isNative ? element.fireEvent('on' + type, document.createEventObject()) : element['_on' + type]++; - }, - - clone = function (element, from, type) { - var events = retrieveEvents(from), obj, k; - obj = type ? events[type] : events; - for (k in obj) { - obj.hasOwnProperty(k) && (type ? add : clone)(element, type || from, type ? obj[k] : k); - } - return element; - }, - - fixEvent = function (e) { - var result = {}; - if (!e) { - return result; - } - var type = e.type, target = e.target || e.srcElement; - result.preventDefault = fixEvent.preventDefault(e); - result.stopPropagation = fixEvent.stopPropagation(e); - result.target = target && target.nodeType == 3 ? target.parentNode : target; - if (~type.indexOf('key')) { - result.keyCode = e.which || e.keyCode; - } else if ((/click|mouse|menu/i).test(type)) { - result.rightClick = e.which == 3 || e.button == 2; - result.pos = { x: 0, y: 0 }; - if (e.pageX || e.pageY) { - result.clientX = e.pageX; - result.clientY = e.pageY; - } else if (e.clientX || e.clientY) { - result.clientX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; - result.clientY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; - } - overOut.test(type) && (result.relatedTarget = e.relatedTarget || e[(type == 'mouseover' ? 'from' : 'to') + 'Element']); - } - for (var k in e) { - if (!(k in result)) { - result[k] = e[k]; - } - } - return result; - }; - - fixEvent.preventDefault = function (e) { - return function () { - if (e.preventDefault) { - e.preventDefault(); - } - else { - e.returnValue = false; - } - }; - }; - - fixEvent.stopPropagation = function (e) { - return function () { - if (e.stopPropagation) { - e.stopPropagation(); - } else { - e.cancelBubble = true; - } - }; - }; - - var nativeEvents = { click: 1, dblclick: 1, mouseup: 1, mousedown: 1, contextmenu: 1, //mouse buttons - mousewheel: 1, DOMMouseScroll: 1, //mouse wheel - mouseover: 1, mouseout: 1, mousemove: 1, selectstart: 1, selectend: 1, //mouse movement - keydown: 1, keypress: 1, keyup: 1, //keyboard - orientationchange: 1, // mobile - touchstart: 1, touchmove: 1, touchend: 1, touchcancel: 1, // touch - gesturestart: 1, gesturechange: 1, gestureend: 1, // gesture - focus: 1, blur: 1, change: 1, reset: 1, select: 1, submit: 1, //form elements - load: 1, unload: 1, beforeunload: 1, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window - error: 1, abort: 1, scroll: 1 }; //misc - - function check(event) { - var related = event.relatedTarget; - if (!related) { - return related == null; - } - return (related != this && related.prefix != 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related)); - } - - var customEvents = { - mouseenter: { base: 'mouseover', condition: check }, - mouseleave: { base: 'mouseout', condition: check }, - mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' } - }; - - var bean = { add: add, remove: remove, clone: clone, fire: fire }; - - var clean = function (el) { - var uid = remove(el).__uid; - if (uid) { - delete collected[uid]; - delete registry[uid]; - } - }; - - if (context[attachEvent]) { - add(context, 'unload', function () { - for (var k in collected) { - collected.hasOwnProperty(k) && clean(collected[k]); - } - context.CollectGarbage && CollectGarbage(); - }); - } - - var oldBean = context.bean; - bean.noConflict = function () { - context.bean = oldBean; - return this; - }; - - (typeof module !== 'undefined' && module.exports) ? - (module.exports = bean) : - (context['bean'] = bean); - -}(this);!function ($) { - var b = bean.noConflict(), - integrate = function (method, type, method2) { - var _args = type ? [type] : []; - return function () { - for (var args, i = 0, l = this.length; i < l; i++) { - args = [this[i]].concat(_args, Array.prototype.slice.call(arguments, 0)); - args.length == 4 && args.push($); - !arguments.length && method == 'add' && type && (method = 'fire'); - b[method].apply(this, args); - } - return this; - }; - }; - - var add = integrate('add'), - remove = integrate('remove'), - fire = integrate('fire'); - - var methods = { - - on: add, - addListener: add, - bind: add, - listen: add, - delegate: add, - - unbind: remove, - unlisten: remove, - removeListener: remove, - undelegate: remove, - - emit: fire, - trigger: fire, - - cloneEvents: integrate('clone'), - - hover: function (enter, leave) { - for (var i = 0, l = this.length; i < l; i++) { - b.add.call(this, this[i], 'mouseenter', enter); - b.add.call(this, this[i], 'mouseleave', leave); - } - return this; - } - }; - - var shortcuts = [ - 'blur', 'change', 'click', 'dblclick', 'error', 'focus', 'focusin', - 'focusout', 'keydown', 'keypress', 'keyup', 'load', 'mousedown', - 'mouseenter', 'mouseleave', 'mouseout', 'mouseover', 'mouseup', - 'resize', 'scroll', 'select', 'submit', 'unload' - ]; - - for (var i = shortcuts.length; i--;) { - var shortcut = shortcuts[i]; - methods[shortcut] = integrate('add', shortcut); - } - - $.ender(methods, true); -}(ender); -/*! - * bonzo.js - copyright @dedfat 2011 - * https://github.com/ded/bonzo - * Follow our software http://twitter.com/dedfat - * MIT License - */ -!function (context) { - - var doc = context.document, - html = doc.documentElement, - query = null, - byTag = 'getElementsByTagName', - specialAttributes = /^checked|value|selected$/, - specialTags = /select|map|fieldset|table|tbody|tr|colgroup/i, - tagMap = { select: 'option', table: 'tbody', tr: 'td' }, - stateAttributes = /^checked|selected$/, - ie = /msie/i.test(navigator.userAgent), - uidList = [], - uuids = 0, - digit = /^-?[\d\.]+$/, - px = 'px', - // commonly used methods - setAttribute = 'setAttribute', - getAttribute = 'getAttribute', - trimReplace = /(^\s*|\s*$)/g, - unitless = { lineHeight: 1, zoom: 1, zIndex: 1, opacity: 1 }; - - function classReg(c) { - return new RegExp("(^|\\s+)" + c + "(\\s+|$)"); - } - - function each(ar, fn, scope) { - for (var i = 0, l = ar.length; i < l; i++) { - fn.call(scope || ar[i], ar[i], i, ar); - } - return ar; - } - - var trim = String.prototype.trim ? - function (s) { - return s.trim(); - } : - function (s) { - return s.replace(trimReplace, ''); - }; - - function camelize(s) { - return s.replace(/-(.)/g, function (m, m1) { - return m1.toUpperCase(); - }); - } - - function is(node) { - return node && node.nodeName && node.nodeType == 1; - } - - function some(ar, fn, scope) { - for (var i = 0, j = ar.length; i < j; ++i) { - if (fn.call(scope, ar[i], i, ar)) { - return true; - } - } - return false; - } - - var getStyle = doc.defaultView && doc.defaultView.getComputedStyle ? - function (el, property) { - var value = null; - if (property == 'float') { - property = 'cssFloat'; - } - var computed = doc.defaultView.getComputedStyle(el, ''); - computed && (value = computed[camelize(property)]); - return el.style[property] || value; - - } : (ie && html.currentStyle) ? - - function (el, property) { - property = camelize(property); - property = property == 'float' ? 'styleFloat' : property; - - if (property == 'opacity') { - var val = 100; - try { - val = el.filters['DXImageTransform.Microsoft.Alpha'].opacity; - } catch (e1) { - try { - val = el.filters('alpha').opacity; - } catch (e2) {} - } - return val / 100; - } - var value = el.currentStyle ? el.currentStyle[property] : null; - return el.style[property] || value; - } : - - function (el, property) { - return el.style[camelize(property)]; - }; - - function insert(target, host, fn) { - var i = 0, self = host || this, r = []; - each(normalize(query ? query(target) : target), function (t) { - each(self, function (el) { - var n = el.cloneNode(true); - fn(t, n); - r[i] = n; - i++; - }); - }, this); - each(r, function (e, i) { - self[i] = e; - }); - self.length = i; - return self; - } - - function xy(el, x, y) { - var $el = bonzo(el), - style = $el.css('position'), - offset = $el.offset(), - rel = 'relative', - isRel = style == rel, - delta = [parseInt($el.css('left'), 10), parseInt($el.css('top'), 10)]; - - if (style == 'static') { - $el.css('position', rel); - style = rel; - } - - isNaN(delta[0]) && (delta[0] = isRel ? 0 : el.offsetLeft); - isNaN(delta[1]) && (delta[1] = isRel ? 0 : el.offsetTop); - - x !== null && (el.style.left = x - offset.left + delta[0] + 'px'); - y !== null && (el.style.top = y - offset.top + delta[1] + 'px'); - - } - - function Bonzo(elements) { - this.length = 0; - this.original = elements; - if (elements) { - elements = typeof elements !== 'string' && - !elements.nodeType && - typeof elements.length !== 'undefined' ? - elements : - [elements]; - this.length = elements.length; - for (var i = 0; i < elements.length; i++) { - this[i] = elements[i]; - } - } - } - - Bonzo.prototype = { - - each: function (fn, scope) { - return each(this, fn, scope); - }, - - map: function (fn, reject) { - var m = [], n, i; - for (i = 0; i < this.length; i++) { - n = fn.call(this, this[i]); - reject ? (reject(n) && m.push(n)) : m.push(n); - } - return m; - }, - - first: function () { - return bonzo(this[0]); - }, - - last: function () { - return bonzo(this[this.length - 1]); - }, - - html: function (h, text) { - var method = text ? - html.textContent == null ? - 'innerText' : - 'textContent' : - 'innerHTML', m; - function append(el, tag) { - while (el.firstChild) { - el.removeChild(el.firstChild); - } - each(normalize(h, tag), function (node) { - el.appendChild(node); - }); - } - return typeof h !== 'undefined' ? - this.each(function (el) { - (m = el.tagName.match(specialTags)) ? - append(el, m[0]) : - (el[method] = h); - }) : - this[0] ? this[0][method] : ''; - }, - - text: function (text) { - return this.html(text, 1); - }, - - addClass: function (c) { - return this.each(function (el) { - this.hasClass(el, c) || (el.className = trim(el.className + ' ' + c)); - }, this); - }, - - removeClass: function (c) { - return this.each(function (el) { - this.hasClass(el, c) && (el.className = trim(el.className.replace(classReg(c), ' '))); - }, this); - }, - - hasClass: function (el, c) { - return typeof c == 'undefined' ? - some(this, function (i) { - return classReg(el).test(i.className); - }) : - classReg(c).test(el.className); - }, - - toggleClass: function (c, condition) { - if (typeof condition !== 'undefined' && !condition) { - return this; - } - return this.each(function (el) { - this.hasClass(el, c) ? - (el.className = trim(el.className.replace(classReg(c), ' '))) : - (el.className = trim(el.className + ' ' + c)); - }, this); - }, - - show: function (type) { - return this.each(function (el) { - el.style.display = type || ''; - }); - }, - - hide: function (elements) { - return this.each(function (el) { - el.style.display = 'none'; - }); - }, - - append: function (node) { - return this.each(function (el) { - each(normalize(node), function (i) { - el.appendChild(i); - }); - }); - }, - - prepend: function (node) { - return this.each(function (el) { - var first = el.firstChild; - each(normalize(node), function (i) { - el.insertBefore(i, first); - }); - }); - }, - - appendTo: function (target, host) { - return insert.call(this, target, host, function (t, el) { - t.appendChild(el); - }); - }, - - prependTo: function (target, host) { - return insert.call(this, target, host, function (t, el) { - t.insertBefore(el, t.firstChild); - }); - }, - - next: function () { - return this.related('nextSibling'); - }, - - previous: function () { - return this.related('previousSibling'); - }, - - related: function (method) { - return this.map( - function (el) { - el = el[method]; - while (el && el.nodeType !== 1) { - el = el[method]; - } - return el || 0; - }, - function (el) { - return el; - } - ); - }, - - before: function (node) { - return this.each(function (el) { - each(bonzo.create(node), function (i) { - el.parentNode.insertBefore(i, el); - }); - }); - }, - - after: function (node) { - return this.each(function (el) { - each(bonzo.create(node), function (i) { - el.parentNode.insertBefore(i, el.nextSibling); - }); - }); - }, - - insertBefore: function (target, host) { - return insert.call(this, target, host, function (t, el) { - t.parentNode.insertBefore(el, t); - }); - }, - - insertAfter: function (target, host) { - return insert.call(this, target, host, function (t, el) { - var sibling = t.nextSibling; - if (sibling) { - t.parentNode.insertBefore(el, sibling); - } - else { - t.parentNode.appendChild(el); - } - }); - }, - - css: function (o, v) { - // is this a request for just getting a style? - if (v === undefined && typeof o == 'string') { - return getStyle(this[0], o); - } - var iter = o; - if (typeof o == 'string') { - iter = {}; - iter[o] = v; - } - - if (ie && iter.opacity) { - // oh this 'ol gamut - iter.filter = 'alpha(opacity=' + (iter.opacity * 100) + ')'; - // give it layout - iter.zoom = o.zoom || 1; - delete iter.opacity; - } - - if (v = iter['float']) { - // float is a reserved style word. w3 uses cssFloat, ie uses styleFloat - ie ? (iter.styleFloat = v) : (iter.cssFloat = v); - delete iter['float']; - } - - var fn = function (el, p, v) { - for (var k in iter) { - if (iter.hasOwnProperty(k)) { - v = iter[k]; - // change "5" to "5px" - unless you're line-height, which is allowed - (p = camelize(k)) && digit.test(v) && !(p in unitless) && (v += px); - el.style[p] = v; - } - } - }; - return this.each(fn); - }, - - offset: function (x, y) { - if (x || y) { - return this.each(function (el) { - xy(el, x, y); - }); - } - var el = this[0]; - var width = el.offsetWidth; - var height = el.offsetHeight; - var top = el.offsetTop; - var left = el.offsetLeft; - while (el = el.offsetParent) { - top = top + el.offsetTop; - left = left + el.offsetLeft; - } - - return { - top: top, - left: left, - height: height, - width: width - }; - }, - - attr: function (k, v) { - var el = this[0]; - return typeof v == 'undefined' ? - specialAttributes.test(k) ? - stateAttributes.test(k) && typeof el[k] == 'string' ? - true : el[k] : el[getAttribute](k) : - this.each(function (el) { - k == 'value' ? (el.value = v) : el[setAttribute](k, v); - }); - }, - - val: function (s) { - return (typeof s == 'string') ? this.attr('value', s) : this[0].value; - }, - - removeAttr: function (k) { - return this.each(function (el) { - el.removeAttribute(k); - }); - }, - - data: function (k, v) { - var el = this[0]; - if (typeof v === 'undefined') { - el[getAttribute]('data-node-uid') || el[setAttribute]('data-node-uid', ++uuids); - var uid = el[getAttribute]('data-node-uid'); - uidList[uid] || (uidList[uid] = {}); - return uidList[uid][k]; - } else { - return this.each(function (el) { - el[getAttribute]('data-node-uid') || el[setAttribute]('data-node-uid', ++uuids); - var uid = el[getAttribute]('data-node-uid'); - var o = {}; - o[k] = v; - uidList[uid] = o; - }); - } - }, - - remove: function () { - return this.each(function (el) { - el.parentNode && el.parentNode.removeChild(el); - }); - }, - - empty: function () { - return this.each(function (el) { - while (el.firstChild) { - el.removeChild(el.firstChild); - } - }); - }, - - detach: function () { - return this.map(function (el) { - return el.parentNode.removeChild(el); - }); - }, - - scrollTop: function (y) { - return scroll.call(this, null, y, 'y'); - }, - - scrollLeft: function (x) { - return scroll.call(this, x, null, 'x'); - } - }; - - function normalize(node, tag) { - return typeof node == 'string' ? bonzo.create(node, tag) : is(node) ? [node] : node; - } - - function scroll(x, y, type) { - var el = this[0]; - if (x == null && y == null) { - return (isBody(el) ? getWindowScroll() : { x: el.scrollLeft, y: el.scrollTop })[type]; - } - if (isBody(el)) { - window.scrollTo(x, y); - } else { - x != null && (el.scrollLeft = x); - y != null && (el.scrollTop = y); - } - return this; - } - - function isBody(element) { - return element === window || (/^(?:body|html)$/i).test(element.tagName); - } - - function getWindowScroll() { - return { x: window.pageXOffset || html.scrollLeft, y: window.pageYOffset || html.scrollTop }; - } - - function bonzo(els, host) { - return new Bonzo(els, host); - } - - bonzo.setQueryEngine = function (q) { - query = q; - delete bonzo.setQueryEngine; - }; - - bonzo.aug = function (o, target) { - for (var k in o) { - o.hasOwnProperty(k) && ((target || Bonzo.prototype)[k] = o[k]); - } - }; - - bonzo.create = function (node, tag) { - return typeof node == 'string' ? - function () { - var t = tag ? tagMap[tag.toLowerCase()] : null; - var el = doc.createElement(t || 'div'), els = []; - if (tag) { - var bitches = node.match(new RegExp("<" + t + ">.+?<\\/" + t + ">", "g")); - each(bitches, function (m) { - m = m.replace(/<(.+)>(.+?)<\/\1>/, '$2'); - var bah = doc.createElement(t); - bah.appendChild(doc.createDocumentFragment(m)); - el.appendChild(bah); - }); - } else { - el.innerHTML = node; - } - var nodes = el.childNodes; - el = el.firstChild; - els.push(el); - while (el = el.nextSibling) { - (el.nodeType == 1) && els.push(el); - } - return els; - - }() : is(node) ? [node.cloneNode(true)] : []; - }; - - bonzo.doc = function () { - var w = html.scrollWidth, - h = html.scrollHeight, - vp = this.viewport(); - return { - width: Math.max(w, vp.width), - height: Math.max(h, vp.height) - }; - }; - - bonzo.firstChild = function (el) { - for (var c = el.childNodes, i = 0, j = (c && c.length) || 0, e; i < j; i++) { - if (c[i].nodeType === 1) { - e = c[j = i]; - } - } - return e; - }; - - bonzo.viewport = function () { - var h = self.innerHeight, - w = self.innerWidth; - ie && (h = html.clientHeight) && (w = html.clientWidth); - return { - width: w, - height: h - }; - }; - - bonzo.isAncestor = 'compareDocumentPosition' in html ? - function (container, element) { - return (container.compareDocumentPosition(element) & 16) == 16; - } : 'contains' in html ? - function (container, element) { - return container !== element && container.contains(element); - } : - function (container, element) { - while (element = element.parentNode) { - if (element === container) { - return true; - } - } - return false; - }; - - var old = context.bonzo; - bonzo.noConflict = function () { - context.bonzo = old; - return this; - }; - context['bonzo'] = bonzo; - -}(this);!function ($) { - - var b = bonzo; - b.setQueryEngine($); - $.ender(b); - $.ender(b(), true); - $.ender({ - create: function (node) { - return $(b.create(node)); - } - }); - - $.id = function (id) { - return $([document.getElementById(id)]); - }; - - function indexOf(ar, val) { - for (var i = 0; i < ar.length; i++) { - if (ar[i] === val) { - return i; - } - } - return -1; - } - - function uniq(ar) { - var a = [], i, j; - label: - for (i = 0; i < ar.length; i++) { - for (j = 0; j < a.length; j++) { - if (a[j] == ar[i]) { - continue label; - } - } - a[a.length] = ar[i]; - } - return a; - } - - $.ender({ - parents: function (selector, closest) { - var collection = $(selector), j, k, p, r = []; - for (j = 0, k = this.length; j < k; j++) { - p = this[j]; - while (p = p.parentNode) { - if (indexOf(collection, p) !== -1) { - r.push(p); - if (closest) break; - } - } - } - return $(uniq(r)); - }, - - closest: function (selector) { - return this.parents(selector, true); - }, - - first: function () { - return $(this[0]); - }, - - last: function () { - return $(this[this.length - 1]); - }, - - next: function () { - return $(b(this).next()); - }, - - previous: function () { - return $(b(this).previous()); - }, - - appendTo: function (t) { - return b(this.selector).appendTo(t, this); - }, - - prependTo: function (t) { - return b(this.selector).prependTo(t, this); - }, - - insertAfter: function (t) { - return b(this.selector).insertAfter(t, this); - }, - - insertBefore: function (t) { - return b(this.selector).insertBefore(t, this); - }, - - siblings: function () { - var i, l, p, r = []; - for (i = 0, l = this.length; i < l; i++) { - p = this[i]; - while (p = p.previousSibling) { - p.nodeType == 1 && r.push(p); - } - p = this[i]; - while (p = p.nextSibling) { - p.nodeType == 1 && r.push(p); - } - } - return $(r); - }, - - children: function () { - var el, r = []; - for (i = 0, l = this.length; i < l; i++) { - if (!(el = b.firstChild(this[i]))) { - continue; - } - r.push(el); - while (el = el.nextSibling) { - el.nodeType == 1 && r.push(el); - } - } - return $(uniq(r)); - }, - - height: function (v) { - return v ? this.css('height', v) : parseInt(this.css('height'), 10); - }, - - width: function (v) { - return v ? this.css('width', v) : parseInt(this.css('width'), 10); - } - }, true); - -}(ender || $); - -!function () { var exports = {}, module = { exports: exports }; !function (doc) { - var loaded = 0, fns = [], ol, f = false, - testEl = doc.createElement('a'), - domContentLoaded = 'DOMContentLoaded', - addEventListener = 'addEventListener', - onreadystatechange = 'onreadystatechange'; - - /^loade|c/.test(doc.readyState) && (loaded = 1); - - function flush() { - loaded = 1; - for (var i = 0, l = fns.length; i < l; i++) { - fns[i](); - } - } - doc[addEventListener] && doc[addEventListener](domContentLoaded, function fn() { - doc.removeEventListener(domContentLoaded, fn, f); - flush(); - }, f); - - - testEl.doScroll && doc.attachEvent(onreadystatechange, (ol = function ol() { - if (/^c/.test(doc.readyState)) { - doc.detachEvent(onreadystatechange, ol); - flush(); - } - })); - - var domReady = testEl.doScroll ? - function (fn) { - self != top ? - !loaded ? - fns.push(fn) : - fn() : - !function () { - try { - testEl.doScroll('left'); - } catch (e) { - return setTimeout(function() { - domReady(fn); - }, 50); - } - fn(); - }(); - } : - function (fn) { - loaded ? fn() : fns.push(fn); - }; - - (typeof module !== 'undefined') && module.exports ? - (module.exports = {domReady: domReady}) : - (window.domReady = domReady); - -}(document); $.ender(module.exports); }.call($); -/*! - * qwery.js - copyright @dedfat - * https://github.com/ded/qwery - * Follow our software http://twitter.com/dedfat - * MIT License - */ - -!function (context, doc) { - - var c, i, j, k, l, m, o, p, r, v, - el, node, len, found, classes, item, items, token, - id = /#([\w\-]+)/, - clas = /\.[\w\-]+/g, - idOnly = /^#([\w\-]+$)/, - classOnly = /^\.([\w\-]+)$/, - tagOnly = /^([\w\-]+)$/, - tagAndOrClass = /^([\w]+)?\.([\w\-]+)$/, - html = doc.documentElement, - tokenizr = /\s(?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\])/, - specialChars = /([.*+?\^=!:${}()|\[\]\/\\])/g, - simple = /^([a-z0-9]+)?(?:([\.\#]+[\w\-\.#]+)?)/, - attr = /\[([\w\-]+)(?:([\|\^\$\*\~]?\=)['"]?([ \w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^]+)["']?)?\]/, - chunker = new RegExp(simple.source + '(' + attr.source + ')?'); - - function array(ar) { - r = []; - for (i = 0, len = ar.length; i < len; i++) { - r[i] = ar[i]; - } - return r; - } - - var cache = function () { - this.c = {}; - }; - cache.prototype = { - g: function (k) { - return this.c[k] || undefined; - }, - s: function (k, v) { - this.c[k] = v; - return v; - } - }; - - var classCache = new cache(), - cleanCache = new cache(), - attrCache = new cache(), - tokenCache = new cache(); - - function q(query) { - return query.match(chunker); - } - - function interpret(whole, tag, idsAndClasses, wholeAttribute, attribute, qualifier, value) { - var m, c, k; - if (tag && this.tagName.toLowerCase() !== tag) { - return false; - } - if (idsAndClasses && (m = idsAndClasses.match(id)) && m[1] !== this.id) { - return false; - } - if (idsAndClasses && (classes = idsAndClasses.match(clas))) { - for (i = classes.length; i--;) { - c = classes[i].slice(1); - if (!(classCache.g(c) || classCache.s(c, new RegExp('(^|\\s+)' + c + '(\\s+|$)'))).test(this.className)) { - return false; - } - } - } - if (wholeAttribute && !value) { - o = this.attributes; - for (k in o) { - if (Object.prototype.hasOwnProperty.call(o, k) && (o[k].name || k) == attribute) { - return this; - } - } - } - if (wholeAttribute && !checkAttr(qualifier, this.getAttribute(attribute) || '', value)) { - return false; - } - return this; - } - - function loopAll(tokens) { - var r = [], token = tokens.pop(), intr = q(token), tag = intr[1] || '*', i, l, els, - root = tokens.length && (m = tokens[0].match(idOnly)) ? doc.getElementById(m[1]) : doc; - if (!root) { - return r; - } - els = root.getElementsByTagName(tag); - for (i = 0, l = els.length; i < l; i++) { - el = els[i]; - if (item = interpret.apply(el, intr)) { - r.push(item); - } - } - return r; - } - - function clean(s) { - return cleanCache.g(s) || cleanCache.s(s, s.replace(specialChars, '\\$1')); - } - - function checkAttr(qualify, actual, val) { - switch (qualify) { - case '=': - return actual == val; - case '^=': - return actual.match(attrCache.g('^=' + val) || attrCache.s('^=' + val, new RegExp('^' + clean(val)))); - case '$=': - return actual.match(attrCache.g('$=' + val) || attrCache.s('$=' + val, new RegExp(clean(val) + '$'))); - case '*=': - return actual.match(attrCache.g(val) || attrCache.s(val, new RegExp(clean(val)))); - case '~=': - return actual.match(attrCache.g('~=' + val) || attrCache.s('~=' + val, new RegExp('(?:^|\\s+)' + clean(val) + '(?:\\s+|$)'))); - case '|=': - return actual.match(attrCache.g('|=' + val) || attrCache.s('|=' + val, new RegExp('^' + clean(val) + '(-|$)'))); - } - return false; - } - - function _qwery(selector) { - var r = [], ret = [], i, l, - tokens = tokenCache.g(selector) || tokenCache.s(selector, selector.split(tokenizr)); - tokens = tokens.slice(0); - if (!tokens.length) { - return r; - } - r = loopAll(tokens); - if (!tokens.length) { - return r; - } - // loop through all descendent tokens - for (j = 0, l = r.length, k = 0; j < l; j++) { - node = r[j]; - p = node; - // loop through each token - for (i = tokens.length; i--;) { - z: // loop through parent nodes - while (p !== html && (p = p.parentNode)) { - if (found = interpret.apply(p, q(tokens[i]))) { - break z; - } - } - } - found && (ret[k++] = node); - } - return ret; - } - - function boilerPlate(selector, _root, fn) { - var root = (typeof _root == 'string') ? fn(_root)[0] : (_root || doc); - if (selector === window || isNode(selector)) { - return !_root || (selector !== window && isNode(root) && isAncestor(selector, root)) ? [selector] : []; - } - if (selector && typeof selector === 'object' && isFinite(selector.length)) { - return array(selector); - } - if (m = selector.match(idOnly)) { - return (el = doc.getElementById(m[1])) ? [el] : []; - } - if (m = selector.match(tagOnly)) { - return array(root.getElementsByTagName(m[1])); - } - return false; - } - - function isNode(el) { - return (el && el.nodeType && (el.nodeType == 1 || el.nodeType == 9)); - } - - function uniq(ar) { - var a = [], i, j; - label: - for (i = 0; i < ar.length; i++) { - for (j = 0; j < a.length; j++) { - if (a[j] == ar[i]) { - continue label; - } - } - a[a.length] = ar[i]; - } - return a; - } - - function qwery(selector, _root) { - var root = (typeof _root == 'string') ? qwery(_root)[0] : (_root || doc); - if (!root || !selector) { - return []; - } - if (m = boilerPlate(selector, _root, qwery)) { - return m; - } - return select(selector, root); - } - - var isAncestor = 'compareDocumentPosition' in html ? - function (element, container) { - return (container.compareDocumentPosition(element) & 16) == 16; - } : 'contains' in html ? - function (element, container) { - container = container == doc || container == window ? html : container; - return container !== element && container.contains(element); - } : - function (element, container) { - while (element = element.parentNode) { - if (element === container) { - return 1; - } - } - return 0; - }, - - select = (doc.querySelector && doc.querySelectorAll) ? - function (selector, root) { - if (doc.getElementsByClassName && (m = selector.match(classOnly))) { - return array((root).getElementsByClassName(m[1])); - } - return array((root).querySelectorAll(selector)); - } : - function (selector, root) { - var result = [], collection, collections = [], i; - if (m = selector.match(tagAndOrClass)) { - items = root.getElementsByTagName(m[1] || '*'); - r = classCache.g(m[2]) || classCache.s(m[2], new RegExp('(^|\\s+)' + m[2] + '(\\s+|$)')); - for (i = 0, l = items.length, j = 0; i < l; i++) { - r.test(items[i].className) && (result[j++] = items[i]); - } - return result; - } - for (i = 0, items = selector.split(','), l = items.length; i < l; i++) { - collections[i] = _qwery(items[i]); - } - for (i = 0, l = collections.length; i < l && (collection = collections[i]); i++) { - var ret = collection; - if (root !== doc) { - ret = []; - for (j = 0, m = collection.length; j < m && (element = collection[j]); j++) { - // make sure element is a descendent of root - isAncestor(element, root) && ret.push(element); - } - } - result = result.concat(ret); - } - return uniq(result); - }; - - qwery.uniq = uniq; - var oldQwery = context.qwery; - qwery.noConflict = function () { - context.qwery = oldQwery; - return this; - }; - context['qwery'] = qwery; - -}(this, document);!function (doc) { - var q = qwery.noConflict(); - function create(node, root) { - var el = (root || doc).createElement('div'), els = []; - el.innerHTML = node; - var nodes = el.childNodes; - el = el.firstChild; - els.push(el); - while (el = el.nextSibling) { - (el.nodeType == 1) && els.push(el); - } - return els; - }; - $._select = function (s, r) { - return /^\s*",a,""].join(""),k.id=i,k.innerHTML+=f,g.appendChild(k),h=c(k,a),k.parentNode.removeChild(k);return!!h},u=function(b){if(a.matchMedia)return matchMedia(b).matches;var c;t("@media "+b+" { #"+i+" { position: absolute; } }",function(b){c=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle).position=="absolute"});return c},v,w={}.hasOwnProperty,x;!A(w,c)&&!A(w.call,c)?x=function(a,b){return w.call(a,b)}:x=function(a,b){return b in a&&A(a.constructor.prototype[b],c)},p.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType){c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"');var d='video/mp4; codecs="avc1.42E01E';c.h264=a.canPlayType(d+'"')||a.canPlayType(d+', mp4a.40.2"'),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"')}}catch(e){}return c};for(var E in p)x(p,E)&&(v=E.toLowerCase(),e[v]=p[E](),s.push((e[v]?"":"no-")+v));y(""),j=l=null,a.attachEvent&&function(){var a=b.createElement("div");a.innerHTML="";return a.childNodes.length!==1}()&&function(a,b){function s(a){var b=-1;while(++b=u.minw)&&(!u.maxw||u.maxw&&l<=u.maxw))m[u.media]||(m[u.media]=[]),m[u.media].push(f[u.rules])}for(var t in g)g[t]&&g[t].parentNode===j&&j.removeChild(g[t]);for(var t in m){var v=c.createElement("style"),w=m[t].join("\n");v.type="text/css",v.media=t,v.styleSheet?v.styleSheet.cssText=w:v.appendChild(c.createTextNode(w)),n.appendChild(v),g.push(v)}j.insertBefore(n,o.nextSibling)}},s=function(a,b){var c=t();if(!!c){c.open("GET",a,!0),c.onreadystatechange=function(){c.readyState==4&&(c.status==200||c.status==304)&&b(c.responseText)};if(c.readyState==4)return;c.send()}},t=function(){var a=!1,b=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new XMLHttpRequest}],c=b.length;while(c--){try{a=b[c]()}catch(d){continue}break}return function(){return a}}();m(),respond.update=m,a.addEventListener?a.addEventListener("resize",u,!1):a.attachEvent&&a.attachEvent("onresize",u)}}(this,Modernizr.mq("only all")),function(a,b,c){function k(a){return!a||a=="loaded"||a=="complete"}function j(){var a=1,b=-1;while(p.length- ++b)if(p[b].s&&!(a=p[b].r))break;a&&g()}function i(a){var c=b.createElement("script"),d;c.src=a.s,c.onreadystatechange=c.onload=function(){!d&&k(c.readyState)&&(d=1,j(),c.onload=c.onreadystatechange=null)},m(function(){d||(d=1,j())},H.errorTimeout),a.e?c.onload():n.parentNode.insertBefore(c,n)}function h(a){var c=b.createElement("link"),d;c.href=a.s,c.rel="stylesheet",c.type="text/css",!a.e&&(w||r)?function a(b){m(function(){if(!d)try{b.sheet.cssRules.length?(d=1,j()):a(b)}catch(c){c.code==1e3||c.message=="security"||c.message=="denied"?(d=1,m(function(){j()},0)):a(b)}},0)}(c):(c.onload=function(){d||(d=1,m(function(){j()},0))},a.e&&c.onload()),m(function(){d||(d=1,j())},H.errorTimeout),!a.e&&n.parentNode.insertBefore(c,n)}function g(){var a=p.shift();q=1,a?a.t?m(function(){a.t=="c"?h(a):i(a)},0):(a(),j()):q=0}function f(a,c,d,e,f,h){function i(){!o&&k(l.readyState)&&(r.r=o=1,!q&&j(),l.onload=l.onreadystatechange=null,m(function(){u.removeChild(l)},0))}var l=b.createElement(a),o=0,r={t:d,s:c,e:h};l.src=l.data=c,!s&&(l.style.display="none"),l.width=l.height="0",a!="object"&&(l.type=d),l.onload=l.onreadystatechange=i,a=="img"?l.onerror=i:a=="script"&&(l.onerror=function(){r.e=r.r=1,g()}),p.splice(e,0,r),u.insertBefore(l,s?null:n),m(function(){o||(u.removeChild(l),r.r=r.e=o=1,j())},H.errorTimeout)}function e(a,b,c){var d=b=="c"?z:y;q=0,b=b||"j",C(a)?f(d,a,b,this.i++,l,c):(p.splice(this.i++,0,a),p.length==1&&g());return this}function d(){var a=H;a.loader={load:e,i:0};return a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=r&&!s,u=s?l:n.parentNode,v=a.opera&&o.call(a.opera)=="[object Opera]",w="webkitAppearance"in l.style,x=w&&"async"in b.createElement("script"),y=r?"object":v||x?"img":"script",z=w?"img":y,A=Array.isArray||function(a){return o.call(a)=="[object Array]"},B=function(a){return typeof a=="object"},C=function(a){return typeof a=="string"},D=function(a){return o.call(a)=="[object Function]"},E=[],F={},G,H;H=function(a){function f(a){var b=a.split("!"),c=E.length,d=b.pop(),e=b.length,f={url:d,origUrl:d,prefixes:b},g,h;for(h=0;h'+alt+''); - } - - $(this).wrap(''); - }); - }); - }; - - externalLinks(); // Delete or comment this line to disable opening external links in new window - appendCaption(); // Delete or comment this line to disable caption - - var mobilenav = $('#mobile-nav'); - - $('html').click(function(){ - mobilenav.find('.on').each(function(){ - $(this).removeClass('on').next().hide(); - }); - }); - - mobilenav.on('click', '.menu .button', function(){ - if (!$(this).hasClass('on')){ - var width = $(this).width() + 42; - $(this).addClass('on').next().show().css({width: width}); - } else { - $(this).removeClass('on').next().hide(); - } - }).on('click', '.search .button', function(){ - if (!$(this).hasClass('on')){ - var width = mobilenav.width() - 51; - mobilenav.children('.menu').children().eq(0).removeClass('on').next().hide(); - $(this).addClass('on').next().show().css({width: width}).children().children().eq(0).focus(); - } else { - $(this).removeClass('on').next().hide().children().children().eq(0).val(''); - } - }).click(function(e){ - e.stopPropagation(); - }); -})(jQuery); \ No newline at end of file diff --git a/javascripts/twitter.js b/javascripts/twitter.js deleted file mode 100644 index 00ecc36..0000000 --- a/javascripts/twitter.js +++ /dev/null @@ -1,92 +0,0 @@ -(function($){ - $.fn.getTwitterFeed = function(userid, count, reply){ - var banner = $(this), - feed = banner.find('.feed'), - interval = 10000, - speed = 500; - - var linkify = function(text){ - text = text.replace(/(https?:\/\/)([\w\-:;?&=+.%#\/]+)/gi, '$2').replace(/(^|\W)@(\w+)/g, '$1@$2').replace(/(^|\W)#(\w+)/g, '$1#$2'); - - return text; - } - - var relativeDate = function(date){ - if (navigator.appName === 'Microsoft Internet Explorer') return ''; - - var unit = { - now: 'Now', - minute: '1 min', - minutes: ' mins', - hour: '1 hr', - hours: ' hrs', - day: 'Yesterday', - days: ' days', - week: '1 week', - weeks: ' weeks' - }; - - var current = new Date(), - tweet = new Date(date), - diff = (((current.getTime() + (1 * 60000)) - tweet.getTime()) / 1000), - day_diff = Math.floor(diff / 86400); - - if (day_diff == 0){ - if (diff < 60) return unit.now; - else if (diff < 120) return unit.minute; - else if (diff < 3600) return Math.floor(diff / 60) + unit.minutes; - else if (diff < 7200) return unit.hour; - else if (diff < 86400) return Math.floor(diff / 3600) + unit.hours; - else return ''; - } else if (day_diff == 1) { - return unit.day; - } else if (day_diff < 7) { - return day_diff + unit.days; - } else if (day_diff == 7) { - return unit.week; - } else if (day_diff > 7) { - return Math.ceil(day_diff / 7) + unit.weeks; - } else { - return ''; - } - } - - if ($(window).width() > 600){ - var url = 'https://api.twitter.com/1/statuses/user_timeline/'+userid+'.json?count='+count+'&exclude_replies='+(reply ? '0' : '1')+'&trim_user=true&callback=?'; - banner.show(); - $.getJSON(url, function(json){ - var length = json.length, - fragment = document.createDocumentFragment(), - counts = 0, - timeout; - - for (var i=0; i'+relativeDate(json[i].created_at)+''; - fragment.appendChild(item); - } - - var play = function(){ - timeout = setTimeout(function(){ - feed.animate({top: '-='+30}, speed, function(){ - $(this).append($(this).children().eq(counts).clone()); - counts++; - play(); - }); - }, interval); - } - - var pause = function(){ - clearTimeout(timeout); - } - - banner.on('mouseenter', pause).on('mouseleave', play) - .children('.loading').hide().end() - .children('.container').show() - .children('.feed').append(fragment); - - play(); - }); - } - }; -})(jQuery); \ No newline at end of file diff --git a/javascripts/xypic.js b/javascripts/xypic.js deleted file mode 100755 index 88b6aab..0000000 --- a/javascripts/xypic.js +++ /dev/null @@ -1,15823 +0,0 @@ -/************************************************************* - * - * MathJax/extensions/TeX/xypic.js - * - * Implements Xy-pic environment. - * - * --------------------------------------------------------------------- - * - * Copyright (c) 2011-2013 Isao Sonobe . - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -MathJax.Extension.xypic = { - version: "0.1", - constants: { - whiteSpaceRegex: /^(\s+|%[^\r\n]*(\r\n|\r|\n)?)+/, - unsupportedBrowserErrorMessage: "Unsupported Browser. Please open with Firefox/Safari/Chrome/Opera" - }, - signalHandler: { - signals: [], - hookedSignals: [], - chains: [], - chainSignal: function (successor, predecessors) { - for (var i = 0; i < predecessors.length; i++) { - MathJax.Extension.xypic.signalHandler.addSignal(predecessors[i]); - } - MathJax.Extension.xypic.signalHandler.chains.push({succ:successor, pred:predecessors}); - }, - addSignal: function (signal) { - var signals = MathJax.Extension.xypic.signalHandler.signals; - for (var i = 0; i < signals.length; i++) { - if (signals[i] === signal) { - return; - } - } - MathJax.Extension.xypic.signalHandler.signals.push(signal); - var handler = MathJax.Extension.xypic.signalHandler.handleSignal(signal); - MathJax.Hub.Register.StartupHook(signal, handler); - }, - handleSignal: function (signal) { - return function () { - MathJax.Extension.xypic.signalHandler.hookedSignals.push(signal); - MathJax.Extension.xypic.signalHandler.handleChains(); - } - }, - handleChains: function () { - var i = 0; - var chains = MathJax.Extension.xypic.signalHandler.chains; - var remainingChains = []; - var invokableSignals = []; - while (i < chains.length) { - var c = chains[i]; - var pred = c.pred; - var invokable = true; - for (var j = 0; j < pred.length; j++) { - var p = pred[j]; - if (!MathJax.Extension.xypic.signalHandler.listenedSignal(p)) { - invokable = false; - break; - } - } - if (invokable) { - invokableSignals.push(c.succ); - } else { - remainingChains.push(c); - } - i++; - } - MathJax.Extension.xypic.signalHandler.chains = remainingChains; - for (i = 0; i < invokableSignals.length; i++) { - MathJax.Hub.Startup.signal.Post(invokableSignals[i]); - } - }, - listenedSignal: function (signal) { - var signals = MathJax.Extension.xypic.signalHandler.hookedSignals; - for (var i = 0; i < signals.length; i++) { - if (signals[i] === signal) { - return true; - } - } - return false; - } - } -} - -// "TeX Xy-pic" depends on "Functional Programming library" and "TeX Jax". -MathJax.Extension.xypic.signalHandler.chainSignal("TeX Xy-pic Require", ["Functional Programming library Ready", "TeX Jax Ready"]); - -// "HTML-CSS Xy-pic Config" depends on "TeX Xy-pic" and "HTML-CSS Jax". -MathJax.Extension.xypic.signalHandler.chainSignal("HTML-CSS Xy-pic Config Require", ["TeX Xy-pic Ready", "HTML-CSS Jax Ready"]); - -// "SVG Xy-pic Config" depends on "TeX Xy-pic" and "SVG Jax". -MathJax.Extension.xypic.signalHandler.chainSignal("SVG Xy-pic Config Require", ["TeX Xy-pic Ready", "SVG Jax Ready"]); - -// "Device-Independent Xy-pic" depends on "TeX Xy-pic" OR "SVG Jax". -MathJax.Extension.xypic.signalHandler.chainSignal("Device-Independent Xy-pic Require", ["HTML-CSS Xy-pic Config Ready"]); -MathJax.Extension.xypic.signalHandler.chainSignal("Device-Independent Xy-pic Require", ["SVG Xy-pic Config Ready"]); - -// "HTML-CSS Xy-pic" depends on "HTML-CSS Xy-pic Config" and "Device-Independent Xy-pic". -MathJax.Extension.xypic.signalHandler.chainSignal("HTML-CSS Xy-pic Require", ["HTML-CSS Xy-pic Config Ready", "Device-Independent Xy-pic Ready"]); - -// "SVG Xy-pic" depends on "SVG Xy-pic Config" and "Device-Independent Xy-pic". -MathJax.Extension.xypic.signalHandler.chainSignal("SVG Xy-pic Require", ["SVG Xy-pic Config Ready", "Device-Independent Xy-pic Ready"]); - -MathJax.Hub.Register.StartupHook("TeX Xy-pic Require",function () { - var FP = MathJax.Extension.fp; - var MML = MathJax.ElementJax.mml; - var TEX = MathJax.InputJax.TeX; - var TEXDEF = TEX.Definitions; - var xypic = MathJax.Extension.xypic; - var AST = xypic.AST = MathJax.Object.Subclass({}); - - MathJax.Hub.Insert(TEXDEF, { - macros: { - //hole: ['Macro', '{\\bbox[3pt]{}}'] - hole: ['Macro', '{\\style{visibility:hidden}{x}}'], - objectstyle: ['Macro', '\\textstyle'], - labelstyle: ['Macro', '\\scriptstyle'], - twocellstyle: ['Macro', '\\scriptstyle'], - xybox: 'Xybox', - xymatrix: 'Xymatrix', - newdir: 'XypicNewdir', - includegraphics: 'Xyincludegraphics' - }, - environment: { - xy: ['ExtensionEnv', null, 'XYpic'] - } - }); - - // override MathJax.InputJax.TeX.formatError function to display parse error. - var tex_formatError = TEX.formatError; - TEX.formatError = function (err, math, displaystyle, script) { - if (err.xyjaxError !== undefined) { - return err.toMML(); - } else { - return tex_formatError(err, math, displaystyle, script); - } - } - - xypic.memoize = function (object, funcName) { - var func = object[funcName]; - var memo = function () { - var value = func.call(this); - var constFunc = function () { - return value; - } - constFunc.reset = reset; - object[funcName] = constFunc; - return value; - } - var reset = function () { - object[funcName] = memo; - } - memo.reset = reset; - reset(); - }; - - AST.xypic = MML.mbase.Subclass({ - Init: function (cmd) { - this.data = []; - this.cmd = cmd; - }, - type: "xypic", - inferRow: false, - defaults: { - mathbackground: MML.INHERIT, - mathcolor: MML.INHERIT, - notation: MML.NOTATION.LONGDIV, - texClass: MML.TEXCLASS.ORD - }, - setTeXclass: MML.mbase.setSeparateTeXclasses, - toString: function () { return this.type + "(" + this.cmd + ")"; } - }); - - AST.xypic.newdir = MML.mbase.Subclass({ - Init: function (cmd) { - this.data = []; - this.cmd = cmd; - }, - type: "newdir", - inferRow: false, - defaults: { - mathbackground: MML.INHERIT, - mathcolor: MML.INHERIT, - notation: MML.NOTATION.LONGDIV, - texClass: MML.TEXCLASS.ORD - }, - setTeXclass: MML.mbase.setSeparateTeXclasses, - toString: function () { return this.type + "(" + this.cmd + ")"; } - }); - - AST.xypic.includegraphics = MML.mbase.Subclass({ - Init: function (cmd) { - this.data = []; - this.cmd = cmd; - }, - type: "includegraphics", - inferRow: false, - defaults: { - mathbackground: MML.INHERIT, - mathcolor: MML.INHERIT, - notation: MML.NOTATION.LONGDIV, - texClass: MML.TEXCLASS.ORD - }, - setTeXclass: MML.mbase.setSeparateTeXclasses, - toString: function () { return this.type + "(" + this.cmd + ")"; } - }); - - // ::= - AST.PosDecor = MathJax.Object.Subclass({ - Init: function (pos, decor) { - this.pos = pos; - this.decor = decor; - }, - toString: function () { - return this.pos.toString() + " " + this.decor; - } - }); - - // - AST.Pos = MathJax.Object.Subclass({}); - // ::= * - AST.Pos.Coord = MathJax.Object.Subclass({ - Init: function (coord, pos2s) { - this.coord = coord; - this.pos2s = pos2s; - }, - toString: function () { - return this.coord.toString() + " " + this.pos2s.mkString(" "); - } - }); - // ::= '+' - AST.Pos.Plus = MathJax.Object.Subclass({ - Init: function (coord) { - this.coord = coord; - }, - toString: function () { - return "+(" + this.coord + ")"; - } - }); - // ::= '-' - AST.Pos.Minus = MathJax.Object.Subclass({ - Init: function (coord) { - this.coord = coord; - }, - toString: function () { - return "-(" + this.coord + ")"; - } - }); - // ::= '!' - AST.Pos.Skew = MathJax.Object.Subclass({ - Init: function (coord) { - this.coord = coord; - }, - toString: function () { - return "!(" + this.coord + ")"; - } - }); - // ::= '.' - AST.Pos.Cover = MathJax.Object.Subclass({ - Init: function (coord) { - this.coord = coord; - }, - toString: function () { - return ".(" + this.coord + ")"; - } - }); - // ::= ',' - AST.Pos.Then = MathJax.Object.Subclass({ - Init: function (coord) { - this.coord = coord; - }, - toString: function () { - return ",(" + this.coord + ")"; - } - }); - // ::= ';' - AST.Pos.SwapPAndC = MathJax.Object.Subclass({ - Init: function (coord) { - this.coord = coord; - }, - toString: function () { - return ";(" + this.coord + ")"; - } - }); - // ::= ':' - AST.Pos.SetBase = MathJax.Object.Subclass({ - Init: function (coord) { - this.coord = coord; - }, - toString: function () { - return ":(" + this.coord + ")"; - } - }); - // ::= '::' - AST.Pos.SetYBase = MathJax.Object.Subclass({ - Init: function (coord) { - this.coord = coord; - }, - toString: function () { - return "::(" + this.coord + ")"; - } - }); - // ::= '**' - AST.Pos.ConnectObject = MathJax.Object.Subclass({ - Init: function (object) { - this.object = object; - }, - toString: function () { - return "**(" + this.object + ")"; - } - }); - // ::= '*' - AST.Pos.DropObject = MathJax.Object.Subclass({ - Init: function (object) { - this.object = object; - }, - toString: function () { - return "*(" + this.object + ")"; - } - }); - // ::= '?' - AST.Pos.Place = MathJax.Object.Subclass({ - Init: function (place) { - this.place = place; - }, - toString: function () { - return "?(" + this.place + ")"; - } - }); - // ::= '@+' - AST.Pos.PushCoord = MathJax.Object.Subclass({ - Init: function (coord) { - this.coord = coord; - }, - toString: function () { - return "@+(" + this.coord + ")"; - } - }); - // ::= '@-' - AST.Pos.EvalCoordThenPop = MathJax.Object.Subclass({ - Init: function (coord) { - this.coord = coord; - }, - toString: function () { - return "@-(" + this.coord + ")"; - } - }); - // ::= '@=' - AST.Pos.LoadStack = MathJax.Object.Subclass({ - Init: function (coord) { - this.coord = coord; - }, - toString: function () { - return "@=(" + this.coord + ")"; - } - }); - // ::= '@@' - AST.Pos.DoCoord = MathJax.Object.Subclass({ - Init: function (coord) { - this.coord = coord; - }, - toString: function () { - return "@@(" + this.coord + ")"; - } - }); - // ::= '@i' - AST.Pos.InitStack = MathJax.Object.Subclass({ - Init: function () { - }, - toString: function () { - return "@i"; - } - }); - // ::= '@(' - AST.Pos.EnterFrame = MathJax.Object.Subclass({ - Init: function () { - }, - toString: function () { - return "@("; - } - }); - // ::= '@)' - AST.Pos.LeaveFrame = MathJax.Object.Subclass({ - Init: function () { - }, - toString: function () { - return "@)"; - } - }); - // ::= '=' '"' '"' - AST.Pos.SavePos = MathJax.Object.Subclass({ - Init: function (id) { - this.id = id; - }, - toString: function () { - return '="' + this.id + '"'; - } - }); - // ::= '=' '"' '"' - AST.Pos.SaveMacro = MathJax.Object.Subclass({ - Init: function (macro, id) { - this.macro = macro; - this.id = id; - }, - toString: function () { - return "=(" + this.macro + ' "' + this.id + '")'; - } - }); - // ::= '=:' '"' '"' - AST.Pos.SaveBase = MathJax.Object.Subclass({ - Init: function (id) { - this.id = id; - }, - toString: function () { - return '=:"' + this.id + '"'; - } - }); - // ::= '=@' '"' '"' - AST.Pos.SaveStack = MathJax.Object.Subclass({ - Init: function (id) { - this.id = id; - }, - toString: function () { - return '=@"' + this.id + '"'; - } - }); - - // - AST.Coord = MathJax.Object.Subclass({}); - // ::= - AST.Coord.Vector = MathJax.Object.Subclass({ - Init: function (vector) { - this.vector = vector; - }, - toString: function () { - return this.vector.toString(); - } - }); - // ::= | 'c' - AST.Coord.C = MathJax.Object.Subclass({ - toString: function () { - return "c"; - } - }); - // ::= 'p' - AST.Coord.P = MathJax.Object.Subclass({ - toString: function () { - return "p"; - } - }); - // ::= 'x' - AST.Coord.X = MathJax.Object.Subclass({ - toString: function () { - return "x"; - } - }); - // ::= 'y' - AST.Coord.Y = MathJax.Object.Subclass({ - toString: function () { - return "y"; - } - }); - // ::= '"' '"' - AST.Coord.Id = MathJax.Object.Subclass({ - Init: function (id) { - this.id = id; - }, - toString: function () { - return '"' + this.id + '"'; - } - }); - // ::= '{' '}' - AST.Coord.Group = MathJax.Object.Subclass({ - Init: function (posDecor) { - this.posDecor = posDecor; - }, - toString: function () { - return '{' + this.posDecor + '}'; - } - }); - // ::= 's' - // ::= 's' '{' '}' - AST.Coord.StackPosition = MathJax.Object.Subclass({ - Init: function (number) { - this.number = number; - }, - toString: function () { - return 's{' + this.number + '}'; - } - }); - - // coordinate for xymatrix - // ::= '[' ('"''"')? ',' ']' - AST.Coord.DeltaRowColumn = MathJax.Object.Subclass({ - /** - * @param {String} prefix name of the xymatrix - * @param {Number} dr rows below - * @param {Number} dc columns right - */ - Init: function (prefix, dr, dc) { - this.prefix = prefix; - this.dr = dr; - this.dc = dc; - }, - toString: function () { - return '[' + (this.prefix === ''? '' : '"' + this.prefix + '"') + this.dr + "," + this.dc + "]"; - } - }); - // coordinate for xymatrix - // ::= '[' ('"''"')? ( 'l' | 'r' | 'u' | 'd' )* ']' - AST.Coord.Hops = MathJax.Object.Subclass({ - /** - * @param {String} prefix name of the xymatrix - * @param {List[String]} hops hops - */ - Init: function (prefix, hops) { - this.prefix = prefix; - this.hops = hops; - }, - toString: function () { - return '[' + (this.prefix === ''? '' : '"' + this.prefix + '"') + this.hops.mkString("") + "]"; - } - }); - // coordinate for xymatrix - // ::= '[' ('"''"')? ( 'l' | 'r' | 'u' | 'd' )+ ']' - AST.Coord.HopsWithPlace = MathJax.Object.Subclass({ - /** - * @param {String} prefix name of the xymatrix - * @param {List[String]} hops hops - * @param {AST.Pos.Place} place place - */ - Init: function (prefix, hops, place) { - this.prefix = prefix; - this.hops = hops; - this.place = place; - }, - toString: function () { - return '[' + (this.prefix === ''? '' : '"' + this.prefix + '"') + this.hops.mkString("") + this.place + "]"; - } - }); - - // - AST.Vector = MathJax.Object.Subclass({}); - // ::= '(' ',' ')' - AST.Vector.InCurBase = MathJax.Object.Subclass({ - Init: function (x, y) { - this.x = x; - this.y = y; - }, - toString: function () { - return "(" + this.x + ", " + this.y + ")"; - } - }); - // ::= '<' ',' '>' - // ::= '<' '>' - AST.Vector.Abs = MathJax.Object.Subclass({ - Init: function (x, y) { - this.x = x; - this.y = y; - }, - toString: function () { - return "<" + this.x + ", " + this.y + ">"; - } - }); - // ::= 'a' '(' ')' - AST.Vector.Angle = MathJax.Object.Subclass({ - Init: function (degree) { - this.degree = degree; - }, - toString: function () { - return "a(" + this.degree + ")"; - } - }); - // ::= '/' '/' - AST.Vector.Dir = MathJax.Object.Subclass({ - Init: function (dir, dimen) { - this.dir = dir; - this.dimen = dimen; - }, - toString: function () { - return "/" + this.dir + " " + this.dimen + "/"; - } - }); - // ::= - // | '(' ')' - AST.Vector.Corner = MathJax.Object.Subclass({ - Init: function (corner, factor) { - this.corner = corner; - this.factor = factor; - }, - toString: function () { - return this.corner.toString() + "(" + this.factor + ")"; - } - }); - - // ::= 'L' | 'R' | 'D' | 'U' - // | 'CL' | 'CR' | 'CD' | 'CU' - // | 'LD' | 'RD' | 'LU' | 'RU' - // | 'E' | 'P' - // | 'A' - AST.Corner = MathJax.Object.Subclass({}); - AST.Corner.L = MathJax.Object.Subclass({ - toString: function () { return "L"; } - }); - AST.Corner.R = MathJax.Object.Subclass({ - toString: function () { return "R"; } - }); - AST.Corner.D = MathJax.Object.Subclass({ - toString: function () { return "D"; } - }); - AST.Corner.U = MathJax.Object.Subclass({ - toString: function () { return "U"; } - }); - AST.Corner.CL = MathJax.Object.Subclass({ - toString: function () { return "CL"; } - }); - AST.Corner.CR = MathJax.Object.Subclass({ - toString: function () { return "CR"; } - }); - AST.Corner.CD = MathJax.Object.Subclass({ - toString: function () { return "CD"; } - }); - AST.Corner.CU = MathJax.Object.Subclass({ - toString: function () { return "CU"; } - }); - AST.Corner.LD = MathJax.Object.Subclass({ - toString: function () { return "LD"; } - }); - AST.Corner.RD = MathJax.Object.Subclass({ - toString: function () { return "RD"; } - }); - AST.Corner.LU = MathJax.Object.Subclass({ - toString: function () { return "LU"; } - }); - AST.Corner.RU = MathJax.Object.Subclass({ - toString: function () { return "RU"; } - }); - AST.Corner.NearestEdgePoint = MathJax.Object.Subclass({ - toString: function () { return "E"; } - }); - AST.Corner.PropEdgePoint = MathJax.Object.Subclass({ - toString: function () { return "P"; } - }); - AST.Corner.Axis = MathJax.Object.Subclass({ - toString: function () { return "A"; } - }); - - // ::= '<' - // ::= '>' - // ::= '(' ')' - // ::= '!' '{' '}' - // ::= - AST.Place = MathJax.Object.Subclass({ - Init: function (shaveP, shaveC, factor, slide) { - this.shaveP = shaveP; - this.shaveC = shaveC; - this.factor = factor; - this.slide = slide; - }, - compound: function (that) { - return AST.Place( - this.shaveP + that.shaveP, - this.shaveC + that.shaveC, - that.factor === undefined? this.factor : that.factor, - that.slide); - }, - toString: function () { - var desc = ""; - for (var l = 0; l < this.shaveP; l++) { - desc += "<"; - } - for (var r = 0; r < this.shaveC; r++) { - desc += ">"; - } - if (this.factor !== undefined) { - desc += "(" + this.factor + ")"; - } - this.slide.dimen.foreach(function (d) { - desc += "/" + d + "/"; - }); - return desc; - } - }); - AST.Place.Factor = MathJax.Object.Subclass({ - Init: function (factor) { - this.factor = factor; - }, - isIntercept: false, - toString: function () { - return this.factor.toString(); - } - }); - AST.Place.Intercept = MathJax.Object.Subclass({ - Init: function (pos) { - this.pos = pos; - }, - isIntercept: true, - toString: function () { - return "!{" + this.pos + "}"; - } - }); - - // ::= - // ::= '/' '/' - AST.Slide = MathJax.Object.Subclass({ - Init: function (dimen) { - this.dimen = dimen; - }, - toString: function () { - return this.dimen.getOrElse(""); - } - }); - - - // ::= * - AST.Object = MathJax.Object.Subclass({ - Init: function (modifiers, object) { - this.modifiers = modifiers; - this.object = object; - }, - dirVariant: function () { return this.object.dirVariant(); }, - dirMain: function () { return this.object.dirMain(); }, - isDir: function () { return this.object.isDir(); }, - toString: function () { - return this.modifiers.mkString() + this.object.toString(); - } - }); - - // - AST.ObjectBox = MathJax.Object.Subclass({ - dirVariant: function () { return undefined; }, - dirMain: function () { return undefined; }, - isDir: function () { return false; }, - isEmpty: false - }); - // ::= '{' '}' - // ::= '{' '}' - AST.ObjectBox.Text = AST.ObjectBox.Subclass({ - Init: function (math) { - this.math = math; - }, - toString: function () { return "{" + this.math.toString() + "}"; } - }); - AST.ObjectBox.Empty = AST.ObjectBox.Subclass({ - isEmpty: true, - toString: function () { return "{}"; } - }); - - // ::= 'xymatrix' - AST.ObjectBox.Xymatrix = AST.ObjectBox.Subclass({ - /** - * @param {AST.Command.Xymatrix} xymatrix xymatrix - */ - Init: function (xymatrix) { - this.xymatrix = xymatrix; - }, - toString: function () { return this.xymatrix.toString(); } - }); - - // ::= '\txt' "].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},x=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return w("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},y={}.hasOwnProperty,z;!C(y,"undefined")&&!C(y.call,"undefined")?z=function(a,b){return y.call(a,b)}:z=function(a,b){return b in a&&C(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e}),q.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c};for(var H in q)z(q,H)&&(v=H.toLowerCase(),e[v]=q[H](),t.push((e[v]?"":"no-")+v));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)z(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},A(""),i=k=null,e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.mq=x,e.testProp=function(a){return E([a])},e.testAllProps=G,e.testStyles=w,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+t.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f') + var mobileNav = $('fieldset.mobile-nav').append('