From b37b5e948c934a7ef58f1d0544aab3f1e18ba6b0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Nov 2025 22:34:52 +0000 Subject: [PATCH 1/4] Modernize documentation site with Hakyll and Bulma CSS This commit migrates the Cloud Haskell documentation site from Jekyll to Hakyll with a modern design using Bulma CSS framework. - Migrated from Jekyll (Ruby) to Hakyll (Haskell) - Added site.hs with comprehensive routing and context handling - Added site.cabal for dependency management - Replaced Bootstrap with Bulma CSS framework - Modern, responsive design inspired by Tokio.rs and Irmin.org - Nord-inspired color scheme for better readability - Custom CSS with improved typography and spacing - Enhanced code syntax highlighting - Hero section on homepage with gradient background - Feature cards highlighting key Cloud Haskell capabilities - Responsive sidebar navigation for documentation - Modern blog layout with card-based design - Improved tutorial pages with breadcrumb navigation - Mobile-friendly responsive design Created new Bulma-based templates: - templates/default.html - Main layout with navbar and footer - templates/index.html - Homepage with hero and feature cards - templates/documentation.html - Docs with sidebar navigation - templates/tutorial.html - Tutorial pages with breadcrumbs - templates/post.html - Individual blog posts - templates/blog.html - Blog listing page - templates/page.html - Generic pages - Renamed _posts/ to posts/ (Hakyll convention) - Archived old Jekyll layouts to old_layouts/ - Archived old includes to old_includes/ - Added templates/ directory for new templates - Enhanced css/ with custom.css and syntax.css - Added README-BUILD.md with detailed build instructions - Updated README.md with overview and quick start - Added MIGRATION.md documenting all changes - Added Makefile.new with convenient build targets - Added .github/workflows/deploy.yml for automated deployment - Configured GitHub Actions to build and deploy to GitHub Pages - Includes caching for faster builds All existing content has been preserved: - Blog posts migrated to posts/ directory - Tutorials remain in tutorials/ - Documentation and other pages unchanged - All static assets (images, PDFs) preserved - Clean, modern navigation with dropdown menus - Sticky sidebar for documentation (desktop) - Hover effects on cards and links - Smooth scrolling and transitions - Consistent spacing and typography - Accessible color contrast - Print-friendly styles This modernisation keeps the existing content while updating the styling and web frameworks used so this will be more maintainable in future and improve the overall user experience. --- .github/workflows/deploy.yml | 75 ++++++ .gitignore | 12 + MIGRATION.md | 245 +++++++++++++++++ Makefile.new | 39 +++ README-BUILD.md | 126 +++++++++ README.md | 40 ++- css/custom.css | 253 ++++++++++++++++++ css/syntax.css | 72 +++++ {_includes => old_includes}/footer.html | 0 {_includes => old_includes}/head.html | 0 {_includes => old_includes}/js.html | 0 {_includes => old_includes}/nav.html | 0 {_layouts => old_layouts}/default.html | 0 {_layouts => old_layouts}/documentation.html | 0 {_layouts => old_layouts}/marketing.html | 0 {_layouts => old_layouts}/page.html | 0 {_layouts => old_layouts}/site.html | 0 {_layouts => old_layouts}/team.html | 0 {_layouts => old_layouts}/tutorial.html | 0 {_layouts => old_layouts}/tutorial2.html | 0 {_layouts => old_layouts}/wiki.html | 0 .../2012-01-21-cloud-haskell-appetiser.md | 0 .../2012-10-04-the-new-cloud-haskell.md | 0 ...communication-patterns-in-cloud-haskell.md | 0 ...mmunication-patterns-in-cloud-haskell-2.md | 0 ...mmunication-patterns-in-cloud-haskell-3.md | 0 ...mmunication-patterns-in-cloud-haskell-4.md | 0 .../2013-01-29-announce-0.4.2.md | 0 .../2013-02-07-problems-with-threaded.md | 0 .../2014-02-06-new-cloud-haskell-website.md | 0 .../2014-02-12-static-pointers.md | 0 .../2014-03-28-network-transport-zeromq.md | 0 ...ud-haskell-release-candidate-on-hackage.md | 0 .../2014-06-13-cloud-haskell-is-live.md | 0 .../2014-08-13-distributed-process-0.5.1.md | 0 .../2015-06-17-cloud-haskell-0.2.2.md | 0 ...-06-30-network-transport-inmemory-0.5.0.md | 0 .../2016-02-19-announce-0.6.0.md | 0 site.cabal | 13 + site.hs | 110 ++++++++ templates/blog.html | 45 ++++ templates/default.html | 159 +++++++++++ templates/documentation.html | 73 +++++ templates/index.html | 196 ++++++++++++++ templates/page.html | 8 + templates/post.html | 35 +++ templates/tutorial.html | 37 +++ 47 files changed, 1535 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/deploy.yml create mode 100644 MIGRATION.md create mode 100644 Makefile.new create mode 100644 README-BUILD.md create mode 100644 css/custom.css create mode 100644 css/syntax.css rename {_includes => old_includes}/footer.html (100%) rename {_includes => old_includes}/head.html (100%) rename {_includes => old_includes}/js.html (100%) rename {_includes => old_includes}/nav.html (100%) rename {_layouts => old_layouts}/default.html (100%) rename {_layouts => old_layouts}/documentation.html (100%) rename {_layouts => old_layouts}/marketing.html (100%) rename {_layouts => old_layouts}/page.html (100%) rename {_layouts => old_layouts}/site.html (100%) rename {_layouts => old_layouts}/team.html (100%) rename {_layouts => old_layouts}/tutorial.html (100%) rename {_layouts => old_layouts}/tutorial2.html (100%) rename {_layouts => old_layouts}/wiki.html (100%) rename {_posts => posts}/2012-01-21-cloud-haskell-appetiser.md (100%) rename {_posts => posts}/2012-10-04-the-new-cloud-haskell.md (100%) rename {_posts => posts}/2012-10-05-communication-patterns-in-cloud-haskell.md (100%) rename {_posts => posts}/2012-10-08-communication-patterns-in-cloud-haskell-2.md (100%) rename {_posts => posts}/2012-10-12-communication-patterns-in-cloud-haskell-3.md (100%) rename {_posts => posts}/2012-10-15-communication-patterns-in-cloud-haskell-4.md (100%) rename {_posts => posts}/2013-01-29-announce-0.4.2.md (100%) rename {_posts => posts}/2013-02-07-problems-with-threaded.md (100%) rename {_posts => posts}/2014-02-06-new-cloud-haskell-website.md (100%) rename {_posts => posts}/2014-02-12-static-pointers.md (100%) rename {_posts => posts}/2014-03-28-network-transport-zeromq.md (100%) rename {_posts => posts}/2014-05-30-cloud-haskell-release-candidate-on-hackage.md (100%) rename {_posts => posts}/2014-06-13-cloud-haskell-is-live.md (100%) rename {_posts => posts}/2014-08-13-distributed-process-0.5.1.md (100%) rename {_posts => posts}/2015-06-17-cloud-haskell-0.2.2.md (100%) rename {_posts => posts}/2015-06-30-network-transport-inmemory-0.5.0.md (100%) rename {_posts => posts}/2016-02-19-announce-0.6.0.md (100%) create mode 100644 site.cabal create mode 100644 site.hs create mode 100644 templates/blog.html create mode 100644 templates/default.html create mode 100644 templates/documentation.html create mode 100644 templates/index.html create mode 100644 templates/page.html create mode 100644 templates/post.html create mode 100644 templates/tutorial.html diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..036fbe7 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,75 @@ +name: Build and Deploy + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Haskell + uses: haskell-actions/setup@v2 + with: + ghc-version: '9.4.8' + cabal-version: '3.10.2.0' + + - name: Cache Cabal + uses: actions/cache@v3 + with: + path: | + ~/.cabal/packages + ~/.cabal/store + dist-newstyle + key: ${{ runner.os }}-cabal-${{ hashFiles('**/*.cabal', '**/cabal.project') }} + restore-keys: | + ${{ runner.os }}-cabal- + + - name: Cache Hakyll + uses: actions/cache@v3 + with: + path: _cache + key: ${{ runner.os }}-hakyll-${{ hashFiles('site.hs') }} + restore-keys: | + ${{ runner.os }}-hakyll- + + - name: Install dependencies + run: cabal update && cabal build --only-dependencies + + - name: Build site generator + run: cabal build + + - name: Build website + run: cabal run site build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: '_site' + + deploy: + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 0bf0b87..3803e48 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,15 @@ .*.swp _site .DS_Store + +# Hakyll +_cache/ +dist/ +dist-newstyle/ +.stack-work/ +site +*.hi +*.o +cabal.project.local +cabal.project.local~ +.ghc.environment.* diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..498b414 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,245 @@ +# Migration from Jekyll to Hakyll + +This document describes the migration of the Cloud Haskell documentation site from Jekyll to Hakyll with Bulma CSS. + +## What Changed + +### Static Site Generator +- **Before:** Jekyll (Ruby-based) +- **After:** Hakyll (Haskell-based) + +### CSS Framework +- **Before:** Bootstrap (older version) +- **After:** Bulma CSS (modern, responsive) + +### Design +- **Before:** Traditional Bootstrap navbar and layout +- **After:** Modern design inspired by Tokio.rs and Irmin.org + - Hero section on homepage + - Feature cards highlighting key capabilities + - Responsive sidebar for documentation + - Modern color scheme (Nord-inspired) + - Improved typography + - Better code syntax highlighting + +## Directory Structure Changes + +### Renamed/Moved +- `_posts/` → `posts/` (Hakyll convention) +- `_layouts/` → `old_layouts/` (archived) +- `_includes/` → `old_includes/` (archived) + +### New Files +``` +site.hs # Hakyll site generator +site.cabal # Cabal package definition +templates/ # New Bulma-based templates + ├── default.html + ├── index.html + ├── documentation.html + ├── tutorial.html + ├── post.html + ├── blog.html + └── page.html +css/ + ├── custom.css # Custom styling + └── syntax.css # Code highlighting +.github/workflows/ + └── deploy.yml # Automated CI/CD +README-BUILD.md # Build instructions +MIGRATION.md # This file +Makefile.new # Build helpers +``` + +## Key Features + +### Homepage +- **Hero Section:** Eye-catching header with gradient background +- **Feature Cards:** Six cards highlighting Cloud Haskell capabilities: + - Distributed Computing + - Concurrent Processes + - Fault Tolerance + - Pluggable Transports + - Platform Libraries + - Type Safety +- **Recent Posts:** Shows latest 3 blog posts +- **Call to Action:** Prominent buttons for getting started + +### Documentation +- **Sidebar Navigation:** Organized menu for easy navigation + - Getting Started + - Core Libraries + - Platform Libraries + - Network Transports + - Tutorials + - Resources +- **Responsive:** Sidebar converts to mobile-friendly menu on small screens + +### Blog +- **Dedicated Blog Page:** Clean listing of all posts +- **Individual Post Pages:** Improved readability with proper spacing +- **Tags:** Support for categorizing posts + +### Tutorials +- **Breadcrumb Navigation:** Easy to track location +- **Info Boxes:** Highlighted sections for important information +- **Code Highlighting:** Nord-themed syntax highlighting + +## Building the Site + +### Old Way (Jekyll) +```bash +bundle install +bundle exec jekyll serve +``` + +### New Way (Hakyll) +```bash +cabal build +cabal run site build +cabal run site watch +``` + +Or use the Makefile: +```bash +make build +make watch +``` + +## Deployment + +### Automated via GitHub Actions +- Builds automatically on push to main/master +- Deploys to GitHub Pages +- Uses caching for faster builds + +### Manual +```bash +make build +# _site directory contains the generated site +``` + +## Content Migration + +All existing content has been preserved: +- ✅ Blog posts (in `posts/`) +- ✅ Tutorials (in `tutorials/`) +- ✅ Documentation (`documentation.md`) +- ✅ Other pages (about, contact, team, wiki) +- ✅ Static assets (images, PDFs, etc.) + +### Markdown Compatibility +Hakyll uses Pandoc for Markdown processing, which is compatible with Jekyll's Kramdown with additional features. + +## Design Elements + +### Color Scheme +```css +Primary: #5e81ac (blue) +Primary Dark: #4c6a8f +Primary Light: #88c0d0 +Secondary: #bf616a (red) +Dark BG: #2e3440 +Light BG: #eceff4 +Code BG: #3b4252 +``` + +### Typography +- System fonts for performance and native feel +- Improved line height (1.7) for readability +- Better heading hierarchy + +### Components +- Cards with hover effects +- Smooth scrolling +- Sticky sidebar navigation +- Responsive navbar with mobile menu +- Modern footer with organized links + +## Browser Support + +The new design uses modern CSS that works in: +- Chrome/Edge (latest) +- Firefox (latest) +- Safari (latest) +- Mobile browsers (iOS Safari, Chrome Mobile) + +## Performance + +### Improvements +- **No jQuery dependency** (used by old Bootstrap) +- **Minimal JavaScript** (only navbar toggle) +- **CDN-hosted Bulma** (cached across sites) +- **Static generation** (fast page loads) + +## Maintenance + +### Adding Content + +**Blog Post:** +```markdown +--- +title: New Post Title +date: 2024-01-01 +--- + +Content here... +``` + +**Tutorial:** +Just edit files in `tutorials/` directory. + +**Documentation:** +Edit `documentation.md` or template in `templates/documentation.html`. + +### Modifying Design + +- **Colors:** Edit `css/custom.css` +- **Layout:** Edit templates in `templates/` +- **Navigation:** Edit `templates/default.html` + +## Troubleshooting + +### Common Issues + +**Build fails:** +- Ensure GHC 8.10+ and Cabal 3.0+ are installed +- Run `cabal update` +- Try `cabal clean` then rebuild + +**Template errors:** +- Check all `$variables$` are defined in site.hs contexts +- Verify template syntax + +**Content not appearing:** +- Check file paths match patterns in site.hs +- Ensure proper YAML front matter + +## Resources + +- [Hakyll Documentation](https://jaspervdj.be/hakyll/) +- [Bulma Documentation](https://bulma.io/documentation/) +- [Tokio.rs](https://tokio.rs) (design inspiration) +- [Irmin.org](https://irmin.org) (design inspiration) + +## Next Steps + +1. ✅ Set up GitHub Pages deployment +2. Test the site thoroughly +3. Gather feedback from users +4. Continue improving documentation +5. Add more tutorials + +## Rollback (if needed) + +If you need to revert to Jekyll: +```bash +mv old_layouts _layouts +mv old_includes _includes +mv posts _posts +# Use old Gemfile and Jekyll configuration +``` + +## Questions? + +Open an issue on GitHub or contact the maintainers. diff --git a/Makefile.new b/Makefile.new new file mode 100644 index 0000000..7952d97 --- /dev/null +++ b/Makefile.new @@ -0,0 +1,39 @@ +.PHONY: build watch clean rebuild deploy help + +# Default target +help: + @echo "Cloud Haskell Documentation Site - Hakyll" + @echo "" + @echo "Available targets:" + @echo " build - Build the site generator and website" + @echo " watch - Start preview server (http://localhost:8000)" + @echo " clean - Clean generated files" + @echo " rebuild - Clean and rebuild everything" + @echo " deploy - Deploy to GitHub Pages" + @echo "" + @echo "First time setup:" + @echo " cabal build" + +# Build the site generator first, then build the site +build: + cabal build + cabal run site build + +# Watch for changes and serve locally +watch: + cabal build + cabal run site watch + +# Clean generated files +clean: + cabal run site clean + rm -rf _cache _site + +# Rebuild from scratch +rebuild: + cabal run site rebuild + +# Deploy (builds _site directory) +deploy: build + @echo "Deploy _site directory to your hosting service" + @echo "For GitHub Pages, commit and push to main/master branch" diff --git a/README-BUILD.md b/README-BUILD.md new file mode 100644 index 0000000..8da0ec9 --- /dev/null +++ b/README-BUILD.md @@ -0,0 +1,126 @@ +# Building the Cloud Haskell Website + +This website is built using [Hakyll](https://jaspervdj.be/hakyll/), a Haskell static site generator, and styled with [Bulma CSS](https://bulma.io/). + +## Prerequisites + +- GHC (Glasgow Haskell Compiler) 8.10 or later +- Cabal 3.0 or later (or Stack) + +## Building the Site + +### Using Cabal + +1. **Build the site generator:** + ```bash + cabal build + ``` + +2. **Build the website:** + ```bash + cabal run site build + ``` + +3. **Preview the website locally:** + ```bash + cabal run site watch + ``` + Then open http://localhost:8000 in your browser. + +## Site Generator Commands + +The site generator (`site`) supports several commands: + +- `build` - Build the website +- `watch` - Start a preview server and watch for changes +- `clean` - Clean generated files +- `rebuild` - Clean and rebuild +- `deploy` - Deploy to GitHub Pages (if configured) + +## Project Structure + +``` +. +├── site.hs # Hakyll site generator configuration +├── site.cabal # Cabal package definition +├── templates/ # HTML templates (with Bulma CSS) +│ ├── default.html # Main layout +│ ├── index.html # Homepage with hero section +│ ├── documentation.html # Docs with sidebar +│ ├── tutorial.html # Tutorial pages +│ ├── post.html # Blog posts +│ └── ... +├── posts/ # Blog posts (Markdown) +├── tutorials/ # Tutorial content +├── css/ # Stylesheets +│ ├── custom.css # Custom styles +│ └── syntax.css # Code syntax highlighting +├── js/ # JavaScript files +├── img/ # Images +├── static/ # Other static assets +└── _site/ # Generated site (git-ignored) +``` + +## Content + +- **Blog posts**: Create `.md` files in `posts/` directory +- **Tutorials**: Edit files in `tutorials/` directory +- **Documentation**: Edit `documentation.md` +- **Homepage**: Edit `index.md` + +All content uses Markdown with YAML front matter: + +```markdown +--- +title: Your Title +date: 2024-01-01 +--- + +Your content here... +``` + +## Design + +The site uses: +- **Bulma CSS** framework for responsive, modern styling +- **Font Awesome** for icons +- **Nord-inspired** color scheme for code highlighting +- **Custom CSS** for additional styling (`css/custom.css`) + +## Deployment + +The site is deployed to GitHub Pages. After building: + +```bash +# The _site directory contains the generated static site +# This can be deployed to GitHub Pages or any static host +``` + +## Development + +When making changes: + +1. Edit source files (templates, content, CSS) +2. Run `cabal run site watch` to preview changes +3. The site auto-rebuilds when files change +4. Refresh your browser to see updates + +## Troubleshooting + +**Build errors:** +- Ensure you have GHC and Cabal installed: `ghc --version` and `cabal --version` +- Try cleaning: `cabal run site clean` then rebuild + +**Port already in use:** +- The preview server uses port 8000 by default +- Stop any other processes using this port + +**Template errors:** +- Check template syntax in `templates/` directory +- Ensure all `$variable$` references are defined in the context + +## Resources + +- [Hakyll Documentation](https://jaspervdj.be/hakyll/) +- [Bulma Documentation](https://bulma.io/documentation/) +- [Pandoc Markdown](https://pandoc.org/MANUAL.html#pandocs-markdown) diff --git a/README.md b/README.md index 20333de..64e8a5f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,38 @@ -haskell-distributed.github.com -============================== +# Cloud Haskell Documentation -haskell-distributed website \ No newline at end of file +Official documentation website for the Haskell Distributed libraries. + +**Live site:** https://haskell-distributed.github.io + +## Overview + +This site provides documentation, tutorials, and resources for Cloud Haskell - a set of libraries for Erlang-style concurrent and distributed programming in Haskell. + +## Technology Stack + +- **Static Site Generator:** [Hakyll](https://jaspervdj.be/hakyll/) (Haskell-based) +- **CSS Framework:** [Bulma](https://bulma.io/) +- **Design Inspiration:** Modern documentation sites like Tokio and Irmin + +## Building the Site + +See [README-BUILD.md](README-BUILD.md) for detailed build instructions. + +Quick start: +```bash +cabal build +cabal run site build +cabal run site watch # Preview at http://localhost:8000 +``` + +## Contributing + +Contributions are welcome! Please feel free to submit pull requests for: +- Documentation improvements +- Tutorial additions +- Bug fixes +- Design enhancements + +## License + +The Cloud Haskell Platform is open source and available under the BSD3 license. \ No newline at end of file diff --git a/css/custom.css b/css/custom.css new file mode 100644 index 0000000..8f3cb42 --- /dev/null +++ b/css/custom.css @@ -0,0 +1,253 @@ +/* Custom CSS for Cloud Haskell Documentation */ + +:root { + --primary-color: #5e81ac; + --primary-dark: #4c6a8f; + --primary-light: #88c0d0; + --secondary-color: #bf616a; + --dark-bg: #2e3440; + --light-bg: #eceff4; + --text-color: #2e3440; + --light-text: #eceff4; + --border-color: #d8dee9; + --code-bg: #3b4252; +} + +/* Typography */ +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + color: var(--text-color); + line-height: 1.7; +} + +.brand-text { + font-size: 1.25rem; + font-weight: 700; + letter-spacing: -0.5px; +} + +/* Hero customization */ +.hero.is-primary { + background: linear-gradient(135deg, #5e81ac 0%, #88c0d0 100%); +} + +.hero.is-medium .hero-body { + padding: 6rem 1.5rem; +} + +/* Cards */ +.card { + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease, box-shadow 0.3s ease; + height: 100%; +} + +.card:hover { + transform: translateY(-4px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); +} + +.card-content { + padding: 2rem; +} + +/* Code blocks */ +pre { + background-color: var(--code-bg) !important; + border-radius: 6px; + padding: 1.5rem !important; + overflow-x: auto; +} + +code { + background-color: #f5f5f5; + border-radius: 3px; + padding: 0.2em 0.4em; + font-size: 0.9em; + font-family: "Fira Code", "Monaco", "Consolas", monospace; +} + +pre code { + background-color: transparent; + padding: 0; + font-size: 0.95em; +} + +/* Sidebar menu */ +.menu { + position: sticky; + top: 1rem; + max-height: calc(100vh - 2rem); + overflow-y: auto; +} + +.menu-label { + color: #7a7a7a; + font-size: 0.85rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-top: 1.5rem; +} + +.menu-label:first-child { + margin-top: 0; +} + +.menu-list a { + border-radius: 4px; + transition: background-color 0.2s ease; +} + +.menu-list a:hover { + background-color: #f5f5f5; + color: var(--primary-color); +} + +.menu-list a.is-active { + background-color: var(--primary-color); + color: white; +} + +/* Content styling */ +.content h1, .content h2, .content h3, .content h4, .content h5, .content h6 { + font-weight: 600; + line-height: 1.3; + margin-top: 2rem; + margin-bottom: 1rem; +} + +.content h1 { + border-bottom: 2px solid var(--border-color); + padding-bottom: 0.5rem; +} + +.content h2 { + border-bottom: 1px solid var(--border-color); + padding-bottom: 0.3rem; +} + +.content a { + color: var(--primary-color); + text-decoration: none; + border-bottom: 1px solid transparent; + transition: border-color 0.2s ease; +} + +.content a:hover { + border-bottom-color: var(--primary-color); +} + +.content blockquote { + border-left: 4px solid var(--primary-color); + background-color: #f9fafb; + padding: 1rem 1.5rem; + margin: 1.5rem 0; +} + +.content table { + width: 100%; + border-collapse: collapse; + margin: 1.5rem 0; +} + +.content table th { + background-color: var(--light-bg); + font-weight: 600; + padding: 0.75rem; + text-align: left; +} + +.content table td { + padding: 0.75rem; + border-top: 1px solid var(--border-color); +} + +.content table tr:hover { + background-color: #fafafa; +} + +/* Buttons */ +.button.is-primary { + background-color: var(--primary-color); + border-color: transparent; + transition: background-color 0.2s ease; +} + +.button.is-primary:hover { + background-color: var(--primary-dark); +} + +/* Footer */ +.footer { + padding: 3rem 1.5rem; + margin-top: 4rem; +} + +.footer ul { + list-style: none; + margin: 0; + padding: 0; +} + +.footer ul li { + margin-bottom: 0.5rem; +} + +.footer a:hover { + text-decoration: underline; +} + +/* Breadcrumbs */ +.breadcrumb { + margin-bottom: 2rem; +} + +.breadcrumb a { + color: var(--primary-color); +} + +/* Notifications */ +.notification.is-info { + background-color: #e8f4fd; + color: #296fa8; +} + +/* Tags */ +.tags .tag { + margin-right: 0.25rem; + margin-bottom: 0.25rem; +} + +/* Responsive adjustments */ +@media screen and (max-width: 768px) { + .hero.is-medium .hero-body { + padding: 3rem 1.5rem; + } + + .menu { + position: static; + max-height: none; + } + + .content h1 { + font-size: 2rem; + } +} + +/* Smooth scrolling */ +html { + scroll-behavior: smooth; +} + +/* Selection color */ +::selection { + background-color: var(--primary-light); + color: white; +} + +/* Box shadow utilities */ +.box, .card { + box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0px 0 1px rgba(10, 10, 10, 0.02); +} diff --git a/css/syntax.css b/css/syntax.css new file mode 100644 index 0000000..ac67bd5 --- /dev/null +++ b/css/syntax.css @@ -0,0 +1,72 @@ +/* Syntax highlighting - Nord theme inspired */ + +.sourceCode { + background-color: #2e3440; + color: #d8dee9; + border-radius: 6px; +} + +/* Keywords */ +.kw { color: #81a1c1; font-weight: bold; } + +/* Data types */ +.dt { color: #8fbcbb; } + +/* Decimals, BaseN, Floats */ +.dv, .bn, .fl { color: #b48ead; } + +/* Characters and Strings */ +.ch, .st { color: #a3be8c; } + +/* Comments */ +.co { color: #616e88; font-style: italic; } + +/* Alerts and Errors */ +.al, .er { color: #bf616a; font-weight: bold; } + +/* Functions */ +.fu { color: #88c0d0; } + +/* Variables */ +.va { color: #d8dee9; } + +/* Operators */ +.op { color: #81a1c1; } + +/* Built-in */ +.bu { color: #8fbcbb; } + +/* Extensions */ +.ex { color: #81a1c1; } + +/* Preprocessor */ +.pp { color: #ebcb8b; } + +/* Attributes */ +.at { color: #ebcb8b; } + +/* Documentation */ +.do { color: #a3be8c; } + +/* Annotations */ +.an { color: #d08770; } + +/* Control Flow */ +.cf { color: #81a1c1; font-weight: bold; } + +/* Imports */ +.im { color: #81a1c1; } + +/* Line numbers */ +.source-line-number { + color: #616e88; +} + +/* Inline code (not in pre blocks) */ +:not(pre) > code { + background-color: #e5e9f0; + color: #bf616a; + padding: 0.2em 0.4em; + border-radius: 3px; + font-size: 0.9em; +} diff --git a/_includes/footer.html b/old_includes/footer.html similarity index 100% rename from _includes/footer.html rename to old_includes/footer.html diff --git a/_includes/head.html b/old_includes/head.html similarity index 100% rename from _includes/head.html rename to old_includes/head.html diff --git a/_includes/js.html b/old_includes/js.html similarity index 100% rename from _includes/js.html rename to old_includes/js.html diff --git a/_includes/nav.html b/old_includes/nav.html similarity index 100% rename from _includes/nav.html rename to old_includes/nav.html diff --git a/_layouts/default.html b/old_layouts/default.html similarity index 100% rename from _layouts/default.html rename to old_layouts/default.html diff --git a/_layouts/documentation.html b/old_layouts/documentation.html similarity index 100% rename from _layouts/documentation.html rename to old_layouts/documentation.html diff --git a/_layouts/marketing.html b/old_layouts/marketing.html similarity index 100% rename from _layouts/marketing.html rename to old_layouts/marketing.html diff --git a/_layouts/page.html b/old_layouts/page.html similarity index 100% rename from _layouts/page.html rename to old_layouts/page.html diff --git a/_layouts/site.html b/old_layouts/site.html similarity index 100% rename from _layouts/site.html rename to old_layouts/site.html diff --git a/_layouts/team.html b/old_layouts/team.html similarity index 100% rename from _layouts/team.html rename to old_layouts/team.html diff --git a/_layouts/tutorial.html b/old_layouts/tutorial.html similarity index 100% rename from _layouts/tutorial.html rename to old_layouts/tutorial.html diff --git a/_layouts/tutorial2.html b/old_layouts/tutorial2.html similarity index 100% rename from _layouts/tutorial2.html rename to old_layouts/tutorial2.html diff --git a/_layouts/wiki.html b/old_layouts/wiki.html similarity index 100% rename from _layouts/wiki.html rename to old_layouts/wiki.html diff --git a/_posts/2012-01-21-cloud-haskell-appetiser.md b/posts/2012-01-21-cloud-haskell-appetiser.md similarity index 100% rename from _posts/2012-01-21-cloud-haskell-appetiser.md rename to posts/2012-01-21-cloud-haskell-appetiser.md diff --git a/_posts/2012-10-04-the-new-cloud-haskell.md b/posts/2012-10-04-the-new-cloud-haskell.md similarity index 100% rename from _posts/2012-10-04-the-new-cloud-haskell.md rename to posts/2012-10-04-the-new-cloud-haskell.md diff --git a/_posts/2012-10-05-communication-patterns-in-cloud-haskell.md b/posts/2012-10-05-communication-patterns-in-cloud-haskell.md similarity index 100% rename from _posts/2012-10-05-communication-patterns-in-cloud-haskell.md rename to posts/2012-10-05-communication-patterns-in-cloud-haskell.md diff --git a/_posts/2012-10-08-communication-patterns-in-cloud-haskell-2.md b/posts/2012-10-08-communication-patterns-in-cloud-haskell-2.md similarity index 100% rename from _posts/2012-10-08-communication-patterns-in-cloud-haskell-2.md rename to posts/2012-10-08-communication-patterns-in-cloud-haskell-2.md diff --git a/_posts/2012-10-12-communication-patterns-in-cloud-haskell-3.md b/posts/2012-10-12-communication-patterns-in-cloud-haskell-3.md similarity index 100% rename from _posts/2012-10-12-communication-patterns-in-cloud-haskell-3.md rename to posts/2012-10-12-communication-patterns-in-cloud-haskell-3.md diff --git a/_posts/2012-10-15-communication-patterns-in-cloud-haskell-4.md b/posts/2012-10-15-communication-patterns-in-cloud-haskell-4.md similarity index 100% rename from _posts/2012-10-15-communication-patterns-in-cloud-haskell-4.md rename to posts/2012-10-15-communication-patterns-in-cloud-haskell-4.md diff --git a/_posts/2013-01-29-announce-0.4.2.md b/posts/2013-01-29-announce-0.4.2.md similarity index 100% rename from _posts/2013-01-29-announce-0.4.2.md rename to posts/2013-01-29-announce-0.4.2.md diff --git a/_posts/2013-02-07-problems-with-threaded.md b/posts/2013-02-07-problems-with-threaded.md similarity index 100% rename from _posts/2013-02-07-problems-with-threaded.md rename to posts/2013-02-07-problems-with-threaded.md diff --git a/_posts/2014-02-06-new-cloud-haskell-website.md b/posts/2014-02-06-new-cloud-haskell-website.md similarity index 100% rename from _posts/2014-02-06-new-cloud-haskell-website.md rename to posts/2014-02-06-new-cloud-haskell-website.md diff --git a/_posts/2014-02-12-static-pointers.md b/posts/2014-02-12-static-pointers.md similarity index 100% rename from _posts/2014-02-12-static-pointers.md rename to posts/2014-02-12-static-pointers.md diff --git a/_posts/2014-03-28-network-transport-zeromq.md b/posts/2014-03-28-network-transport-zeromq.md similarity index 100% rename from _posts/2014-03-28-network-transport-zeromq.md rename to posts/2014-03-28-network-transport-zeromq.md diff --git a/_posts/2014-05-30-cloud-haskell-release-candidate-on-hackage.md b/posts/2014-05-30-cloud-haskell-release-candidate-on-hackage.md similarity index 100% rename from _posts/2014-05-30-cloud-haskell-release-candidate-on-hackage.md rename to posts/2014-05-30-cloud-haskell-release-candidate-on-hackage.md diff --git a/_posts/2014-06-13-cloud-haskell-is-live.md b/posts/2014-06-13-cloud-haskell-is-live.md similarity index 100% rename from _posts/2014-06-13-cloud-haskell-is-live.md rename to posts/2014-06-13-cloud-haskell-is-live.md diff --git a/_posts/2014-08-13-distributed-process-0.5.1.md b/posts/2014-08-13-distributed-process-0.5.1.md similarity index 100% rename from _posts/2014-08-13-distributed-process-0.5.1.md rename to posts/2014-08-13-distributed-process-0.5.1.md diff --git a/_posts/2015-06-17-cloud-haskell-0.2.2.md b/posts/2015-06-17-cloud-haskell-0.2.2.md similarity index 100% rename from _posts/2015-06-17-cloud-haskell-0.2.2.md rename to posts/2015-06-17-cloud-haskell-0.2.2.md diff --git a/_posts/2015-06-30-network-transport-inmemory-0.5.0.md b/posts/2015-06-30-network-transport-inmemory-0.5.0.md similarity index 100% rename from _posts/2015-06-30-network-transport-inmemory-0.5.0.md rename to posts/2015-06-30-network-transport-inmemory-0.5.0.md diff --git a/_posts/2016-02-19-announce-0.6.0.md b/posts/2016-02-19-announce-0.6.0.md similarity index 100% rename from _posts/2016-02-19-announce-0.6.0.md rename to posts/2016-02-19-announce-0.6.0.md diff --git a/site.cabal b/site.cabal new file mode 100644 index 0000000..ad9a711 --- /dev/null +++ b/site.cabal @@ -0,0 +1,13 @@ +cabal-version: 2.0 +name: haskell-distributed-site +version: 0.1.0.0 +build-type: Simple + +executable site + main-is: site.hs + build-depends: base == 4.* + , hakyll >= 4.15 && < 4.17 + , filepath + , pandoc >= 2.9 + ghc-options: -threaded + default-language: Haskell2010 diff --git a/site.hs b/site.hs new file mode 100644 index 0000000..1292f9b --- /dev/null +++ b/site.hs @@ -0,0 +1,110 @@ +{-# LANGUAGE OverloadedStrings #-} +import Data.Monoid (mappend) +import Hakyll +import System.FilePath (takeBaseName, takeDirectory, ()) + +-------------------------------------------------------------------------------- +main :: IO () +main = hakyll $ do + -- Copy static files + match ("img/**" .||. "js/**" .||. "ico/**" .||. "static/**") $ do + route idRoute + compile copyFileCompiler + + -- Copy and compress CSS + match "css/**" $ do + route idRoute + compile compressCssCompiler + + -- Build tags from posts + tags <- buildTags "posts/**" (fromCapture "tags/*.html") + + -- Process blog posts + match "posts/**" $ do + route $ setExtension "html" + compile $ pandocCompiler + >>= loadAndApplyTemplate "templates/post.html" postCtx + >>= loadAndApplyTemplate "templates/default.html" (postCtxWithTags tags) + >>= relativizeUrls + + -- Create post list + create ["blog.html"] $ do + route idRoute + compile $ do + posts <- recentFirst =<< loadAll "posts/**" + let archiveCtx = + listField "posts" postCtx (return posts) `mappend` + constField "title" "Blog" `mappend` + constField "page-type" "blog" `mappend` + defaultContext + + makeItem "" + >>= loadAndApplyTemplate "templates/blog.html" archiveCtx + >>= loadAndApplyTemplate "templates/default.html" archiveCtx + >>= relativizeUrls + + -- Process tutorials + match "tutorials/**" $ do + route $ setExtension "html" + compile $ pandocCompiler + >>= loadAndApplyTemplate "templates/tutorial.html" postCtx + >>= loadAndApplyTemplate "templates/default.html" defaultContext + >>= relativizeUrls + + -- Process wiki pages + match "wiki/**" $ do + route $ setExtension "html" + compile $ pandocCompiler + >>= loadAndApplyTemplate "templates/page.html" postCtx + >>= loadAndApplyTemplate "templates/default.html" defaultContext + >>= relativizeUrls + + -- Index page (special handling for hero layout) + match "index.md" $ do + route $ setExtension "html" + compile $ do + posts <- fmap (take 3) . recentFirst =<< loadAll "posts/**" + let indexCtx = + listField "posts" postCtx (return posts) `mappend` + constField "page-type" "home" `mappend` + constField "title" "Home" `mappend` + defaultContext + + pandocCompiler + >>= applyAsTemplate indexCtx + >>= loadAndApplyTemplate "templates/index.html" indexCtx + >>= loadAndApplyTemplate "templates/default.html" indexCtx + >>= relativizeUrls + + -- Other pages (about, contact, documentation, etc.) + match (fromList ["about.html", "contact.html", "terms.md", "team.md", "wiki.md", "changes.md"]) $ do + route $ setExtension "html" + compile $ pandocCompiler + >>= loadAndApplyTemplate "templates/page.html" postCtx + >>= loadAndApplyTemplate "templates/default.html" defaultContext + >>= relativizeUrls + + -- Documentation page (with sidebar) + match "documentation.md" $ do + route $ setExtension "html" + compile $ do + let docCtx = + constField "page-type" "documentation" `mappend` + postCtx + + pandocCompiler + >>= loadAndApplyTemplate "templates/documentation.html" docCtx + >>= loadAndApplyTemplate "templates/default.html" docCtx + >>= relativizeUrls + + -- Read templates + match "templates/*" $ compile templateBodyCompiler + +-------------------------------------------------------------------------------- +postCtx :: Context String +postCtx = + dateField "date" "%B %e, %Y" `mappend` + defaultContext + +postCtxWithTags :: Tags -> Context String +postCtxWithTags tags = tagsField "tags" tags `mappend` postCtx diff --git a/templates/blog.html b/templates/blog.html new file mode 100644 index 0000000..817ae18 --- /dev/null +++ b/templates/blog.html @@ -0,0 +1,45 @@ +
+
+
+

Blog

+

Updates and announcements from the Cloud Haskell team

+
+
+
+ +
+
+
+
+ $if(posts)$ +
+ $for(posts)$ + + $endfor$ +
+ $else$ +
+
+ No posts yet. +
+
+ $endif$ +
+
+
+
diff --git a/templates/default.html b/templates/default.html new file mode 100644 index 0000000..3db20f3 --- /dev/null +++ b/templates/default.html @@ -0,0 +1,159 @@ + + + + + + $title$ - Cloud Haskell + + + + + + + + + + + + + + + + + + +
+ $body$ +
+ + + + + + + + diff --git a/templates/documentation.html b/templates/documentation.html new file mode 100644 index 0000000..49b9ae5 --- /dev/null +++ b/templates/documentation.html @@ -0,0 +1,73 @@ +
+
+
+ +
+ +
+ + +
+
+

$title$

+ $body$ +
+
+
+
+
diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..f8eb70e --- /dev/null +++ b/templates/index.html @@ -0,0 +1,196 @@ + +
+
+
+

+ Cloud Haskell +

+

+ Erlang-style concurrent and distributed programming in Haskell +

+ +
+
+
+ + +
+
+
+ $body$ +
+
+
+ + +
+
+

Features

+
+
+
+
+
+ + + +
+

Distributed Computing

+

+ Build distributed applications with explicit message passing between nodes. + Send messages across networks with type-safe, serializable values. +

+
+
+
+ +
+
+
+
+ + + +
+

Concurrent Processes

+

+ Lightweight processes with asynchronous message passing. + Inspired by Erlang's actor model for building highly concurrent applications. +

+
+
+
+ +
+
+
+
+ + + +
+

Fault Tolerance

+

+ Supervise processes and handle failures gracefully. + Monitor and link processes for automatic error recovery and resilient systems. +

+
+
+
+ +
+
+
+
+ + + +
+

Pluggable Transports

+

+ Generic network transport API supporting TCP, in-memory, and custom backends. + Switch between transports without changing application code. +

+
+
+
+ +
+
+
+
+ + + +
+

Platform Libraries

+

+ Rich ecosystem of libraries for common patterns: client/server, async tasks, + registries, execution control, and more. +

+
+
+
+ +
+
+
+
+ + + +
+

Type Safe

+

+ Leverage Haskell's strong type system for distributed programming. + Static closures ensure type safety across network boundaries. +

+
+
+
+
+
+
+ + +$if(posts)$ +
+
+

Latest Updates

+
+ $for(posts)$ +
+
+
+

$date$

+

+ $title$ +

+
+
+
+ $endfor$ +
+ +
+
+$endif$ + + +
+
+

Ready to Get Started?

+

+ Build distributed systems with the power of Haskell +

+ +
+
diff --git a/templates/page.html b/templates/page.html new file mode 100644 index 0000000..5e84ea4 --- /dev/null +++ b/templates/page.html @@ -0,0 +1,8 @@ +
+
+
+

$title$

+ $body$ +
+
+
diff --git a/templates/post.html b/templates/post.html new file mode 100644 index 0000000..4381192 --- /dev/null +++ b/templates/post.html @@ -0,0 +1,35 @@ +
+
+
+
+
+

$title$

+

+ + $if(tags)$ + + $for(tags)$ + $tags$ + $endfor$ + + $endif$ +

+ + $body$ + +
+ +
+
+
+
+
diff --git a/templates/tutorial.html b/templates/tutorial.html new file mode 100644 index 0000000..16800b3 --- /dev/null +++ b/templates/tutorial.html @@ -0,0 +1,37 @@ +
+
+
+
+
+ + +

$title$

+ +
+

Tutorial: Follow along with code examples to learn Cloud Haskell.

+
+ + $body$ + +
+ +
+
+
+
+
From ebd3216021d21c31b59e8b01a395eb6ce6988afa Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Nov 2025 00:04:12 +0000 Subject: [PATCH 2/4] Fix code syntax highlighting for better readability The previous code highlighting had poor contrast - grey blocks made Haskell code unreadable. This commit fixes the styling: ## Changes ### Code Block Improvements - Set explicit light text color (#d8dee9) on dark background (#3b4252) - Added proper contrast for all code elements - Enhanced visual separation with subtle border and shadow - Improved line-height (1.6) for better readability - Added margin spacing around code blocks ### Inline Code Styling - Light background (#f5f5f5) with red accent (#bf616a) - Clear distinction from block code - Medium font-weight (500) for emphasis - Proper padding and border-radius ### Syntax Highlighting Colors (Nord-inspired) - Keywords: #81a1c1 (blue, bold) - Types: #8fbcbb (cyan) - Strings: #a3be8c (green) - Functions: #88c0d0 (light cyan) - Comments: #616e88 (grey, italic) - Numbers: #b48ead (purple) - Operators: #81a1c1 (blue) ### Technical Details - Handles multiple Pandoc output formats (.sourceCode, div.sourceCode) - Plain code blocks (without syntax) also styled consistently - Font stack: "Fira Code", Monaco, Consolas, Courier New, monospace - All code elements properly inherit colors when nested ### Files Modified - css/custom.css: Base code block styling - css/syntax.css: Syntax highlighting colors and classes - code-test.md: Test page to verify styling (can be removed) The new styling provides excellent contrast and readability while maintaining the modern, Nord-inspired aesthetic of the site. --- code-test.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ css/custom.css | 11 ++++++++- css/syntax.css | 32 ++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 code-test.md diff --git a/code-test.md b/code-test.md new file mode 100644 index 0000000..cf03aee --- /dev/null +++ b/code-test.md @@ -0,0 +1,63 @@ +--- +title: Code Highlighting Test +--- + +# Code Highlighting Test + +This page demonstrates the code highlighting styles. + +## Inline Code + +Here is some `inline code` that should be readable. + +You can use inline code like `import Control.Distributed.Process` in your text. + +## Code Blocks + +### Haskell Code + +```haskell +-- Simple process example +import Control.Distributed.Process +import Control.Distributed.Process.Node + +-- | Simple ping server +pingServer :: Process () +pingServer = do + say "Ping server started" + forever $ do + ping <- expect :: Process String + say $ "Got ping: " ++ ping + send ping "pong" + +-- | Main function +main :: IO () +main = do + putStrLn "Starting distributed system" + node <- newLocalNode + runProcess node pingServer +``` + +### Plain Code Block + +``` +This is a plain code block +Without syntax highlighting +But it should still be readable +``` + +## Features + +- **Dark background**: `#3b4252` (Nord-inspired) +- **Light text**: `#d8dee9` for high contrast +- **Colored syntax**: Keywords, types, strings, etc. +- **Readable inline code**: Light background with colored text + +## Color Palette + +- Keywords: `#81a1c1` (blue) +- Types: `#8fbcbb` (cyan) +- Strings: `#a3be8c` (green) +- Functions: `#88c0d0` (light cyan) +- Comments: `#616e88` (grey, italic) +- Numbers: `#b48ead` (purple) diff --git a/css/custom.css b/css/custom.css index 8f3cb42..9bf71c4 100644 --- a/css/custom.css +++ b/css/custom.css @@ -58,6 +58,10 @@ pre { border-radius: 6px; padding: 1.5rem !important; overflow-x: auto; + color: #d8dee9; + margin: 1.5rem 0; + border: 1px solid rgba(216, 222, 233, 0.1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } code { @@ -65,13 +69,18 @@ code { border-radius: 3px; padding: 0.2em 0.4em; font-size: 0.9em; - font-family: "Fira Code", "Monaco", "Consolas", monospace; + font-family: "Fira Code", "Monaco", "Consolas", "Courier New", monospace; + color: #bf616a; + font-weight: 500; } pre code { background-color: transparent; padding: 0; font-size: 0.95em; + color: #d8dee9; + font-weight: normal; + line-height: 1.6; } /* Sidebar menu */ diff --git a/css/syntax.css b/css/syntax.css index ac67bd5..7fe873f 100644 --- a/css/syntax.css +++ b/css/syntax.css @@ -1,9 +1,20 @@ /* Syntax highlighting - Nord theme inspired */ .sourceCode { - background-color: #2e3440; + background-color: #3b4252; color: #d8dee9; border-radius: 6px; + padding: 1.5rem; +} + +pre.sourceCode { + background-color: #3b4252 !important; + color: #d8dee9; +} + +code.sourceCode { + background-color: transparent; + color: #d8dee9; } /* Keywords */ @@ -70,3 +81,22 @@ border-radius: 3px; font-size: 0.9em; } + +/* Ensure all code blocks are styled properly */ +div.sourceCode { + background-color: #3b4252; + border-radius: 6px; + padding: 1.5rem; + overflow-x: auto; + margin: 1rem 0; +} + +/* Plain code blocks without syntax highlighting */ +pre:not(.sourceCode) { + background-color: #3b4252; + color: #d8dee9; +} + +pre:not(.sourceCode) code { + color: #d8dee9; +} From 666daeb7a3be2879905b31e53500347697de0129 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Nov 2025 00:11:13 +0000 Subject: [PATCH 3/4] Convert Jekyll highlight syntax to Markdown code fences Jekyll's {% highlight %} syntax is not recognized by Hakyll/Pandoc. This commit converts all 133 code blocks to use standard Markdown code fences (```lang) for proper syntax highlighting. ## Changes ### Conversion Applied - Replaced `{% highlight %}` with ````` - Replaced `{% endhighlight %}` with ```` - Total: 133 code blocks converted across 12 files ### Files Converted **Tutorials (6 files):** - tutorials/1ch.md - Getting Started - tutorials/2ch.md - Managing Topologies - tutorials/3ch.md - Getting to know Processes - tutorials/4ch.md - Managed Processes - tutorials/6ch.md - Advanced Managed Processes - tutorials/tutorial-NT2.md - Programming with Network.Transport **Documentation (2 files):** - documentation.md - Main documentation - wiki.md - Wiki index **Wiki Pages (4 files):** - wiki/networktransport.md - wiki/contributing.md - wiki/newtransports.md - wiki/newdesign.md ### Impact Hakyll uses Pandoc for Markdown processing, which recognizes: - ````haskell` - Haskell code blocks (majority of conversions) - ````bash` - Shell commands - Plain ``` - Generic code blocks These will now be properly syntax highlighted using the Nord-inspired color scheme defined in css/syntax.css. ### Verification Before: 133 instances of {% highlight %} After: 0 instances of {% highlight %} All code blocks now use standard Markdown syntax compatible with Pandoc's syntax highlighting engine. --- documentation.md | 28 +++++----- tutorials/1ch.md | 44 ++++++++-------- tutorials/2ch.md | 16 +++--- tutorials/3ch.md | 64 +++++++++++----------- tutorials/4ch.md | 72 ++++++++++++------------- tutorials/6ch.md | 44 ++++++++-------- tutorials/tutorial-NT2.md | 76 +++++++++++++-------------- wiki.md | 4 +- wiki/contributing.md | 12 ++--- wiki/networktransport.md | 40 +++++++------- wiki/newdesign.md | 108 +++++++++++++++++++------------------- wiki/newtransports.md | 24 ++++----- 12 files changed, 266 insertions(+), 266 deletions(-) diff --git a/documentation.md b/documentation.md index b59ed15..9723229 100644 --- a/documentation.md +++ b/documentation.md @@ -156,12 +156,12 @@ required configuration and pass the returned opaque handle to the `Node` API in order to establish a new, connected, running node. More involved setups are, of course, possible; The simplest use of the API is thus -{% highlight haskell %} +```haskell main :: IO main = do Right transport <- createTransport "127.0.0.1" "10080" defaultTCPParameters node1 <- newLocalNode transport initRemoteTable -{% endhighlight %} +``` Here we can see that the application depends explicitly on the `defaultTCPParameters` and `createTransport` functions from @@ -207,9 +207,9 @@ Processes reside on nodes, which in our implementation map directly to the `Control.Distributed.Processes.Node` module. Given a configured `Network.Transport` backend, starting a new node is fairly simple: -{% highlight haskell %} +```haskell newLocalNode :: Transport -> IO LocalNode -{% endhighlight %} +``` Once this function returns, the node will be *up and running* and able to interact with other nodes and host processes. It is possible to start more @@ -219,10 +219,10 @@ backend. Given a new node, there are two primitives for starting a new process. -{% highlight haskell %} +```haskell forkProcess :: LocalNode -> Process () -> IO ProcessId runProcess :: LocalNode -> Process () -> IO () -{% endhighlight %} +``` Once we've spawned some processes, they can communicate with one another using the messaging primitives provided by [distributed-process][distributed-process], @@ -260,7 +260,7 @@ types, forcing us to undertake dynamic type checking at runtime. We create channels with a call to `newChan`, and send/receive on them using the `{send,receive}Chan` primitives: -{% highlight haskell %} +```haskell channelsDemo :: Process () channelsDemo = do (sp, rp) <- newChan :: Process (SendPort String, ReceivePort String) @@ -271,7 +271,7 @@ channelsDemo = do -- receive on a channel m <- receiveChan rp say $ show m -{% endhighlight %} +``` Channels are particularly useful when you are sending a message that needs a response, because we know exactly where to look for the reply. @@ -353,7 +353,7 @@ simply. ------ -{% highlight haskell %} +```haskell demoAsync :: Process () demoAsync = do -- spawning a new task is fairly easy - this one is linked @@ -379,7 +379,7 @@ demoAsync = do (AsyncDone res) -> say (show res) -- a finished task/result AsyncCancelled -> say "it was cancelled!?" AsyncFailed (DiedException r) -> say $ "it failed: " ++ (show r) -{% endhighlight %} +``` ------ @@ -407,7 +407,7 @@ The type of asynchronous task definitions comes in two flavours, one for local nodes which require no remote-table or static serialisation dictionary, and another for tasks you wish to execute on remote nodes. -{% highlight haskell %} +```haskell -- | A task to be performed asynchronously. data AsyncTask a = AsyncTask @@ -423,7 +423,7 @@ data AsyncTask a = , asyncTaskProc :: Closure (Process a) -- ^ the task to be performed, wrapped in a closure environment } -{% endhighlight %} +``` The API for `Async` is fairly rich, so reading the haddocks is suggested. @@ -452,7 +452,7 @@ out the obvious differences. A process implemented with `ManagedProcess` can present a type safe API to its callers (and the server side code too!), although that's not its primary benefit. For a very simplified example: -{% highlight haskell %} +```haskell add :: ProcessId -> Double -> Double -> Process Double add sid x y = call sid (Add x y) @@ -475,7 +475,7 @@ launchMathServer = divByZero :: Process (Either DivByZero Double) divByZero = return $ Left DivByZero -{% endhighlight %} +``` Apart from the types and the imports, that is a complete definition. Whilst it's not so obvious what's going on here, the key point is that the invocation diff --git a/tutorials/1ch.md b/tutorials/1ch.md index 4eb07d2..9208224 100644 --- a/tutorials/1ch.md +++ b/tutorials/1ch.md @@ -26,9 +26,9 @@ and [GitHub](https://github.com). Starting a new Cloud Haskell project using `stack` is as easy as -{% highlight bash %} +```bash $ stack new -{% endhighlight %} +``` in a fresh new directory. This will populate the directory with a number of files, chiefly `stack.yaml` and `*.cabal` metadata files @@ -48,22 +48,22 @@ types that Cloud Haskell needs at a minimum in order to run. In `app/Main.hs`, we start with our imports: -{% highlight haskell %} +```haskell import Network.Transport.TCP (createTransport, defaultTCPParameters) import Control.Distributed.Process import Control.Distributed.Process.Node -{% endhighlight %} +``` Our TCP network transport backend needs an IP address and port to get started with: -{% highlight haskell %} +```haskell main :: IO () main = do Right t <- createTransport "127.0.0.1" "10501" defaultTCPParameters node <- newLocalNode t initRemoteTable .... -{% endhighlight %} +``` And now we have a running node. @@ -75,7 +75,7 @@ a `Process` action to run, because our concurrent code will run in the id can be used to send messages to the running process - here we will send one to ourselves! -{% highlight haskell %} +```haskell -- in main _ <- runProcess node $ do -- get our own process id @@ -84,7 +84,7 @@ to ourselves! hello <- expect :: Process String liftIO $ putStrLn hello return () -{% endhighlight %} +``` Note that we haven't deadlocked our own thread by sending to and receiving from its mailbox in this fashion. Sending messages is a completely @@ -100,7 +100,7 @@ there is. Messages in the mailbox are ordered by time of arrival. Let's spawn two processes on the same node and have them talk to each other: -{% highlight haskell %} +```haskell import Control.Concurrent (threadDelay) import Control.Monad (forever) import Control.Distributed.Process @@ -141,7 +141,7 @@ main = do -- Without the following delay, the process sometimes exits before the messages are exchanged. liftIO $ threadDelay 2000000 -{% endhighlight %} +``` Note that we've used `receiveWait` this time around to get a message. `receiveWait` and similarly named functions can be used with the @@ -166,10 +166,10 @@ again. Processes may send any datum whose type implements the `Serializable` typeclass, defined as: -{% highlight haskell %} +```haskell class (Binary a, Typeable) => Serializable a instance (Binary a, Typeable a) => Serializable a -{% endhighlight %} +``` That is, any type that is `Binary` and `Typeable` is `Serializable`. This is the case for most of Cloud Haskell's primitive types as well as many standard @@ -177,14 +177,14 @@ data types. For custom data types, the `Typeable` instance is always given by the compiler, and the `Binary` instance can be auto-generated too in most cases, e.g.: -{% highlight haskell %} +```haskell {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} data T = T Int Char deriving (Generic, Typeable) instance Binary T -{% endhighlight %} +``` ### Spawning Remote Processes @@ -212,9 +212,9 @@ Static actions are not easy to construct by hand, but fortunately Cloud Haskell provides a little bit of Template Haskell to help. If `f :: T1 -> T2` then -{% highlight haskell %} +```haskell $(mkClosure 'f) :: T1 -> Closure T2 -{% endhighlight %} +``` You can turn any top-level unary function into a `Closure` using `mkClosure`. For curried functions, you'll need to uncurry them first (i.e. "tuple up" the @@ -228,27 +228,27 @@ We need to configure our remote table (see the [API reference][6] for more details) and the easiest way to do this, is to let the library generate the relevant code for us. For example: -{% highlight haskell %} +```haskell sampleTask :: (TimeInterval, String) -> Process () sampleTask (t, s) = sleep t >> say s remotable ['sampleTask] -{% endhighlight %} +``` The last line is a top-level Template Haskell splice. At the call site for `spawn`, we can construct a `Closure` corresponding to an application of `sampleTask` like so: -{% highlight haskell %} +```haskell ($(mkClosure 'sampleTask) (seconds 2, "foobar")) -{% endhighlight %} +``` The call to `remotable` implicitly generates a remote table by inserting a top-level definition `__remoteTable :: RemoteTable -> RemoteTable` in our module for us. We compose this with other remote tables in order to come up with a final, merged remote table for all modules in our program: -{% highlight haskell %} +```haskell {-# LANGUAGE TemplateHaskell #-} import Control.Concurrent (threadDelay) @@ -275,7 +275,7 @@ main = do _ <- spawnLocal $ sampleTask (1 :: Int, "using spawnLocal") pid <- spawn us $ $(mkClosure 'sampleTask) (1 :: Int, "using spawn") liftIO $ threadDelay 2000000 -{% endhighlight %} +``` In the above example, we spawn `sampleTask` on node `us` in two different ways: diff --git a/tutorials/2ch.md b/tutorials/2ch.md index 280eb12..1bb66c3 100644 --- a/tutorials/2ch.md +++ b/tutorials/2ch.md @@ -23,7 +23,7 @@ node names/addresses, or by using some form of registrar such as DNS-SD/Bonjour) Here is an example program built against the [`simplelocalnet`][1] backend, that periodically searches for a list of peer nodes, and sends a message to a registered (named) process on each. -{% highlight haskell %} +```haskell import System.Environment (getArgs) import Control.Distributed.Process import Control.Distributed.Process.Node (initRemoteTable, runProcess) @@ -38,7 +38,7 @@ main = do peers <- findPeers backend 1000000 runProcess node $ forM_ peers $ \peer -> nsendRemote peer "echo-server" "hello!" -{% endhighlight %} +``` Clearly the program isn't very useful, but it illustrates the two key concepts that `simplelocalnet` relies on. Firstly, that we `initializeBackend` in order to get @@ -52,7 +52,7 @@ Here we simply rehash the controller/worker example from the `simplelocalnet` do With the same imports as the example above, we add a no-op worker and a controller that takes a list of its (known) workers, which it prints out before terminating them all. -{% highlight haskell %} +```haskell main :: IO () main = do args <- getArgs @@ -65,18 +65,18 @@ main = do backend <- initializeBackend host port initRemoteTable startSlave backend -{% endhighlight %} +``` And the controller node is defined thus: -{% highlight haskell %} +```haskell controller :: Backend -> [NodeId] -> Process () controller backend workers = do -- Do something interesting with the workers liftIO . putStrLn $ "Workers: " ++ show workers -- Terminate the workers when the controller terminates (this is optional) terminateAllSlaves backend -{% endhighlight %} +``` ### Other Topologies and Backends @@ -88,7 +88,7 @@ discovers and maintains knowledge of it's peers. Here is an example of node discovery using the [`distributed-process-p2p`][3] backend: -{% highlight haskell %} +```haskell import System.Environment (getArgs) import Control.Distributed.Process import Control.Distributed.Process.Node (initRemoteTable) @@ -103,7 +103,7 @@ main = do runProcess node $ forever $ do findPeers >>= mapM_ $ \peer -> nsend peer "echo-server" "hello!" -{% endhighlight %} +``` [1]: http://hackage.haskell.org/package/distributed-process-simplelocalnet [2]: http://hackage.haskell.org/package/distributed-process-azure diff --git a/tutorials/3ch.md b/tutorials/3ch.md index 175dda6..52d1b4b 100644 --- a/tutorials/3ch.md +++ b/tutorials/3ch.md @@ -52,7 +52,7 @@ scanning the mailbox, it is dequeued and returned, otherwise the caller (i.e., the calling thread/process) is blocked until a message of the expected type is delivered to the mailbox. Let's take a look at this in action: -{% highlight haskell %} +```haskell demo :: Process () demo = do listener <- spawnLocal listen @@ -69,7 +69,7 @@ demo = do (say . show) second (say . show) third send third () -{% endhighlight %} +``` This program will print `"hello"`, then `Nothing` and finally `pid://...`. The first `expect` - labelled "third" because of the order in which we @@ -92,13 +92,13 @@ whole `receive` expression evaluates to. Consider the following snippet: -{% highlight haskell %} +```haskell usingReceive = do () <- receiveWait [ match (\(s :: String) -> say s) , match (\(i :: Int) -> say $ show i) ] -{% endhighlight %} +``` Note that each of the matches in the list must evaluate to the same type, as the type signature indicates: `receiveWait :: [Match b] -> Process b`. @@ -125,26 +125,26 @@ simply dequeues _any_ messages it receives and forwards them to some other proce In order to dequeue messages regardless of their type, this code relies on the `matchAny` primitive, which has the following type: -{% highlight haskell %} +```haskell matchAny :: forall b. (Message -> Process b) -> Match b -{% endhighlight %} +``` Since forwarding _raw messages_ (without decoding them first) is a common pattern in Cloud Haskell programs, there is also a primitive to do that for us: -{% highlight haskell %} +```haskell forward :: Message -> ProcessId -> Process () -{% endhighlight %} +``` Given these types, we can see that in order to combine `matchAny` with `forward` we need to either _flip_ `forward` and apply the `ProcessId` (leaving us with the required type `Message -> Process b`) or use a lambda - the actual implementation does the latter and looks like this: -{% highlight haskell %} +```haskell relay :: ProcessId -> Process () relay !pid = forever' $ receiveWait [ matchAny (\m -> forward m pid) ] -{% endhighlight %} +``` This is pretty useful, but since `matchAny` operates on the raw `Message` type, we're limited in what we can do with the messages we receive. In order to delve @@ -154,11 +154,11 @@ the result to see whether the decoding succeeds or not. There are two primitives we can use to that effect: `unwrapMessage` and `handleMessage`. Their types look like this: -{% highlight haskell %} +```haskell unwrapMessage :: forall m a. (Monad m, Serializable a) => Message -> m (Maybe a) handleMessage :: forall m a b. (Monad m, Serializable a) => Message -> (a -> m b) -> m (Maybe b) -{% endhighlight %} +``` Of the two, `unwrapMessage` is the simpler, taking a raw `Message` and evaluating to `Maybe a` before returning that value in the monad `m`. If the type of the raw `Message` @@ -175,9 +175,9 @@ evaluates some input of type `a` and returns `Process Bool`, allowing us to run `Process` code in order to decide whether or not the `a` is eligible to be forwarded to the relay `ProcessId`. The type of `proxy` is thus: -{% highlight haskell %} +```haskell proxy :: Serializable a => ProcessId -> (a -> Process Bool) -> Process () -{% endhighlight %} +``` Since `matchAny` operates on `(Message -> Process b)` and `handleMessage` operates on `a -> Process b` we can compose these to make our proxy server. We must not forward @@ -185,7 +185,7 @@ messages for which the predicate function evaluates to `Just False`, nor can we forward messages which the predicate function is unable to evaluate due to type incompatibility. This leaves us with the definition found in distributed-process: -{% highlight haskell %} +```haskell proxy pid proc = do receiveWait [ matchAny (\m -> do @@ -196,7 +196,7 @@ proxy pid proc = do Nothing -> return ()) -- un-routable / cannot decode ] proxy pid proc -{% endhighlight %} +``` Beyond simple relays and proxies, the raw message handling capabilities available in distributed-process can be utilised to develop highly generic message processing code. @@ -246,7 +246,7 @@ be busy processing other events. On the other hand, the [`die`][7] primitive thr In practise, this means the following two functions could behave quite differently at runtime: -{% highlight haskell %} +```haskell -- this will never print anything... demo1 = die "Boom" >> expect >>= say @@ -256,7 +256,7 @@ demo2 = do self <- getSelfPid exit self "Boom" expect >>= say -{% endhighlight %} +``` The `ProcessExitException` type holds a _reason_ field, which is serialised as a raw `Message`. This exception type is exported, so it is possible to catch these _exit signals_ and decide how @@ -283,14 +283,14 @@ of the type it is waiting for). Even though the child terminates "normally", our is also terminated since `link` will _link the lifetime of two processes together_ regardless of exit reasons. -{% highlight haskell %} +```haskell demo = do pid <- spawnLocal $ expect >>= return link pid send pid () () <- expect return () -{% endhighlight %} +``` The medium that link failures uses to signal exit conditions is the same as exit and kill signals - asynchronous exceptions. Once again, it is a bad idea to rely on this (not least @@ -312,7 +312,7 @@ monitors can be used to determine both when and _how_ a process has terminated. away in distributed-process-extras, the `linkOnFailure` primitive works in exactly this way, only terminating the caller if the subject terminates abnormally. Let's take a look... -{% highlight haskell %} +```haskell linkOnFailure them = do us <- getSelfPid tid <- liftIO $ myThreadId @@ -330,7 +330,7 @@ linkOnFailure them = do case reason of DiedNormal -> return () _ -> liftIO $ throwTo tid (ProcessLinkException us reason) -{% endhighlight %} +``` As we can see, this code makes use of monitors to track both processes involved in the link. In order to track _both_ processes and react to changes in their status, it is @@ -384,7 +384,7 @@ This example is a bit contrived and over-simplified but illustrates the concept. Consider the `fetchUser` function below, it runs in the `AppProcess` monad which provides the configuration settings required to connect to the database: -{% highlight haskell %} +```haskell import Data.ByteString (ByteString) import Control.Monad.Reader @@ -414,7 +414,7 @@ openDB = do closeDB :: DB.Connection -> AppProcess () closeDB db = liftIO (DB.close db) -{% endhighlight %} +``` So this would mostly work but it is not complete. What happens if an exception is thrown by the `query` function? Your open database handle may not be @@ -422,14 +422,14 @@ closed. Typically we manage this with the [bracket][brkt] function. In the base library, [bracket][brkt] is defined in Control.Exception with this signature: -{% highlight haskell %} +```haskell bracket :: IO a --^ computation to run first ("acquire resource") -> (a -> IO b) --^ computation to run last ("release resource") -> (a -> IO c) --^ computation to run in-between -> IO c -{% endhighlight %} +``` Great! We pass an IO action that acquires a resource; `bracket` passes that resource to a function which takes the resource and runs another action. @@ -447,7 +447,7 @@ It is perfectly possible to write our own bracket; `distributed-process` does th for the `Process` monad (which is itself a newtyped ReaderT stack). Here is how that is done: -{% highlight haskell %} +```haskell -- | Lift 'Control.Exception.bracket' bracket :: Process a -> (a -> Process b) -> (a -> Process c) -> Process c bracket before after thing = @@ -473,7 +473,7 @@ mask p = do onException :: Process a -> Process b -> Process a onException p what = p `catch` \e -> do _ <- what liftIO $ throwIO (e :: SomeException) -{% endhighlight %} +``` `distributed-process` needs to do this sort of thing to keep its dependency list small, but do we really want to write this for every transformer stack @@ -491,7 +491,7 @@ explanation written by Michael Snoyman which is available [here][mctrlt]. in the Haskell base library. For example, [Control.Exception.Lifted][lexc] has a definition of bracket that looks like this: -{% highlight haskell %} +```haskell bracket :: MonadBaseControl IO m => m a --^ computation to run first ("acquire resource") @@ -499,7 +499,7 @@ bracket :: MonadBaseControl IO m -> (a -> m c) --^ computation to run in-between -> m c -{% endhighlight %} +``` It is just the same as the version found in base, except it is generalized to work with actions in any monad that implements [MonadBaseControl IO][mbc]. [monad-control][mctrl] defines @@ -511,7 +511,7 @@ provides orphan instances of the `Process` type for both [MonadBase IO][mb] and After importing these, we can rewrite our `fetchUser` function to use the instance of bracket provided by [lifted-base][lbase]. -{% highlight haskell %} +```haskell -- ... import Control.Distributed.Process.MonadBaseControl () @@ -526,7 +526,7 @@ fetchUser email = $ \db -> liftIO $ DB.query db email -{% endhighlight %} +``` [lifted-base][lbase] also provides conveniences like [MVar][lmvar] and other concurrency primitives that operate in [MonadBase IO][mb]. One benefit here is that your code is not sprinkled with diff --git a/tutorials/4ch.md b/tutorials/4ch.md index fddc1d4..25f3556 100644 --- a/tutorials/4ch.md +++ b/tutorials/4ch.md @@ -38,7 +38,7 @@ also provides hooks for error handling (in case of either server code crashing _or_ exit signals dispatched to the server process from elsewhere) and _cleanup_ code to be run on termination/shutdown. -{% highlight haskell %} +```haskell myServer :: ProcessDefinition MyStateType myServer = ProcessDefinition { @@ -75,7 +75,7 @@ myServer = , unhandledMessagePolicy = Drop -- Terminate | (DeadLetter ProcessId) } -{% endhighlight %} +``` When defining a protocol between client and server, we typically decide on a set of types the server will handle and possibly maps these to replies we @@ -112,7 +112,7 @@ that math server that does just that: ---- -{% highlight haskell %} +```haskell module MathServer ( -- client facing API add @@ -151,7 +151,7 @@ launchMathServer = , unhandledMessagePolicy = Drop } in spawnLocal $ serve () (statelessInit Infinity) server >> return () -{% endhighlight %} +``` This style of programming will already be familiar if you've used some @@ -177,7 +177,7 @@ make the server code any prettier (since it has to reply to the channel explicitly, rather than just evaluating to a result), it does reduce the likelihood of runtime errors somewhat. -{% highlight haskell %} +```haskell -- This is the only way clients can get a message through to us that -- we will respond to, and since we control the type(s), there is no -- risk of decoding errors on the server. The /call/ API ensures that @@ -194,7 +194,7 @@ launchMathServer = , unhandledMessagePolicy = Drop } in spawnLocal $ serve () (statelessInit Infinity) server >> return () -{% endhighlight %} +``` Ensuring that only valid types are sent to the server is relatively simple, given that we do not expose the client directly to `call` and write our own @@ -220,7 +220,7 @@ this function in a test/demo application, you'll need to block the main thread for a while to wait for the server to receive the message and print out the result. -{% highlight haskell %} +```haskell printSum :: ProcessId -> Double -> Double -> Process () printSum sid = cast sid . Add @@ -233,7 +233,7 @@ launchMathServer = , unhandledMessagePolicy = Drop } in spawnLocal $ serve () (statelessInit Infinity) server >> return () -{% endhighlight %} +``` Of course this is a toy example - why defer simple computations like addition @@ -278,7 +278,7 @@ manner suits them: The type of a task will be `Closure (Process a)` and the server will explicitly return an /either/ value with `Left String` for errors and `Right a` for successful results. -{% highlight haskell %} +```haskell -- enqueues the task in the pool and blocks -- the caller until the task is complete executeTask :: forall s a . (Addressable s, Serializable a) @@ -286,7 +286,7 @@ executeTask :: forall s a . (Addressable s, Serializable a) -> Closure (Process a) -> Process (Either String a) executeTask sid t = call sid t -{% endhighlight %} +``` Remember that in Cloud Haskell, the only way to communicate with a process (apart from introducing scoped concurrency primitives like `MVar` or using @@ -337,9 +337,9 @@ run to completion) and communicate the result (or failure) to the original calle This means our pool state will need to be parameterised by the result type it will accept in its closures. So now we have the beginnings of our state type: -{% highlight haskell %} +```haskell data BlockingQueue a = BlockingQueue -{% endhighlight %} +``` ### Making use of Async @@ -365,17 +365,17 @@ size limit), we hold the client ref and the closure, but no monitor ref. We'll use a data structure that support FIFO ordering semantics for this, since that's probably what clients will expect of something calling itself a "queue". -{% highlight haskell %} +```haskell data BlockingQueue a = BlockingQueue { poolSize :: SizeLimit , active :: [(MonitorRef, CallRef (Either ExitReason a), Async a)] , accepted :: Seq (CallRef (Either ExitReason a), Closure (Process a)) } -{% endhighlight %} +``` Our queue-like behaviour is fairly simple to define using `Data.Sequence`: -{% highlight haskell %} +```haskell enqueue :: Seq a -> a -> Seq a enqueue s a = a <| s @@ -387,7 +387,7 @@ getR s = case (viewr s) of EmptyR -> Nothing a -> Just a -{% endhighlight %} +``` Now, to turn that `Closure` environment into a thunk we can evaluate, we'll use the @@ -397,11 +397,11 @@ the async API in detail here, except to point out that the call to `async` spawn new process to do the actual work and returns a handle that we can use to query for the result. -{% highlight haskell %} +```haskell proc <- unClosure task' asyncHandle <- async proc ref <- monitorAsync asyncHandle -{% endhighlight %} +``` We can now implement the `acceptTask` function, which the server will use to handle submitted tasks. The signature of our function must be compatible with the message @@ -418,7 +418,7 @@ with a possible reply to one of the `call` derivatives. Since we're deferring ou until later, we will use `noReply_`, which creates a `ProcessAction` for us, telling the server to continue receiving messages. -{% highlight haskell %} +```haskell storeTask :: Serializable a => BlockingQueue a -> CallRef (Either ExitReason a) @@ -442,7 +442,7 @@ acceptTask s@(BlockingQueue sz' runQueue taskQueue) from task' = ref <- monitorAsync asyncHandle let taskEntry = (ref, from, asyncHandle) return s { active = (taskEntry:runQueue) } -{% endhighlight %} +``` If we're at capacity, we add the task (and caller) to the `accepted` queue, otherwise we launch and monitor the task using `async` and stash the monitor @@ -481,7 +481,7 @@ and since there's no expected reply, as with `cast`, we simply return a `Process telling the server what to do next - in this case, to `continue` reading from the mailbox. -{% highlight haskell %} +```haskell taskComplete :: forall a . Serializable a => BlockingQueue a -> ProcessMonitorNotification @@ -521,7 +521,7 @@ deleteFromRunQueue :: (MonitorRef, CallRef (Either ExitReason a), Async a) -> [(MonitorRef, CallRef (Either ExitReason a), Async a)] -> [(MonitorRef, CallRef (Either ExitReason a), Async a)] deleteFromRunQueue c@(p, _, _) runQ = deleteBy (\_ (b, _, _) -> b == p) c runQ -{% endhighlight %} +``` We've dealt with mapping the `AsyncResult` to `Either` values, which we *could* have left to the caller, but this makes the client facing API much simpler to work with. @@ -543,14 +543,14 @@ In order to spell things out for the compiler, we need to put a type signature in place at the call site for `storeTask`, so our final construct for that handler is thus: -{% highlight haskell %} +```haskell handleCallFrom (\s f (p :: Closure (Process a)) -> storeTask s f p) -{% endhighlight %} +``` No such thing is required for `taskComplete`, as there's no ambiguity about its type. Our process definition is now finished, and here it is: -{% highlight haskell %} +```haskell defaultProcess { apiHandlers = [ handleCallFrom (\s f (p :: Closure (Process a)) -> storeTask s f p) @@ -558,7 +558,7 @@ defaultProcess { ] , infoHandlers = [ handleInfo taskComplete ] } -{% endhighlight %} +``` Starting the server takes a bit of work: `ManagedProcess` provides several utility functions to help with spawning and running processes. The `serve` @@ -567,7 +567,7 @@ that must generate the initial state and set up the server's receive timeout, then the process definition which we've already encountered. For more details about starting managed processes, see the haddocks. -{% highlight haskell %} +```haskell run :: forall a . (Serializable a) => Process (InitResult (BlockingQueue a)) -> Process () @@ -585,25 +585,25 @@ pool :: forall a . Serializable a => SizeLimit -> Process (InitResult (BlockingQueue a)) pool sz' = return $ InitOk (BlockingQueue sz' [] Seq.empty) Infinity -{% endhighlight %} +``` ### Putting it all together Defining tasks is as simple as making them remote-worthy: -{% highlight haskell %} +```haskell sampleTask :: (TimeInterval, String) -> Process String sampleTask (t, s) = sleep t >> return s $(remotable ['sampleTask]) -{% endhighlight %} +``` And executing them is just as simple too. -{% highlight haskell %} +```haskell tsk <- return $ ($(mkClosure 'sampleTask) (seconds 2, "foobar")) executeTask taskQueuePid tsk -{% endhighlight %} +``` Starting up the server itself locally or on a remote node, is just a matter of combining `spawn` or `spawnLocal` with `start`. We can go a step further though, @@ -621,17 +621,17 @@ able to pass this handle to the managed process `call` API, so we define an instance of the `Resolvable` typeclass for it, which makes a (default) instance of `Routable` available, which is exactly what `call` is expecting: -{% highlight haskell %} +```haskell newtype TaskQueue a = TaskQueue { unQueue :: ProcessId } instance Resolvable (TaskQueue a) where resolve = return . unQueue -{% endhighlight %} +``` Finally, we write a `start` function that returns this handle and change the signature of `executeTask` to match it: -{% highlight haskell %} +```haskell start :: forall a . (Serializable a) => SizeLimit -> Process (TaskQueue a) @@ -644,7 +644,7 @@ executeTask :: (Serializable a) -> Closure (Process a) -> Process (Either ExitReason a) executeTask sid t = call sid t -{% endhighlight %} +``` ---------- diff --git a/tutorials/6ch.md b/tutorials/6ch.md index 29a32c6..357fce7 100644 --- a/tutorials/6ch.md +++ b/tutorials/6ch.md @@ -55,7 +55,7 @@ that our clients only communicate with us in well-known ways. Let's take a look at this in action, revisiting the well-trodden _math server_ example from our previous tutorials: -{% highlight haskell %} +```haskell module MathServer ( -- client facing API MathServer() @@ -82,7 +82,7 @@ launchMathServer = launch >>= return . MathServer , unhandledMessagePolicy = Drop } in spawnLocal $ start () (statelessInit Infinity) server >> return () -{% endhighlight %} +``` What we've changed here is the _handle_ clients use to communicate with the process, hiding the `ProcessId` behind a newtype and forcing client code to @@ -118,17 +118,17 @@ We can alleviate this problem using phantom type parameters, storing only the real `ProcessId` we need to communicate with the server, whilst utilising the compiler to ensure the correct types are assumed at both ends. -{% highlight haskell %} +```haskell data Registry k v = Registry { registryPid :: ProcessId } deriving (Typeable, Generic, Show, Eq) instance (Keyable k, Serializable v) => Binary (Registry k v) where -{% endhighlight %} +``` In order to start our registry, we need to know the specific `k` and `v` types, but we do not real values of these, so we use scoped type variables to reify them when creating the `Registry` handle: -{% highlight haskell %} +```haskell start :: forall k v. (Keyable k, Serializable v) => Process (Registry k v) start = return . Registry =<< spawnLocal (run (undefined :: Registry k v)) @@ -136,7 +136,7 @@ run :: forall k v. (Keyable k, Serializable v) => Registry k v -> Process () run _ = MP.pserve () (const $ return $ InitOk initState Infinity) serverDefinition -- etc.... -{% endhighlight %} +``` Having wrapped the `ProcessId` in a newtype that ensures the types with which the server was initialised are respected by clients, we use the same approach @@ -144,11 +144,11 @@ as earlier to force clients of our API to interact with the server not only using the requisite call/cast protocol, but also providing the correct types in the form of a valid handle. -{% highlight haskell %} +```haskell addProperty :: (Keyable k, Serializable v) => Registry k v -> k -> v -> Process RegisterKeyReply addProperty reg k v = .... -{% endhighlight %} +``` So long as we only expose `Registry` newtype construction via our `start` API, clients cannot forge a registry handle and both client and server can rely on @@ -175,10 +175,10 @@ are your friend. By providing a `Resolvable` instance, you can expose your decision to only expose the `ProcessId` via a typeclass) the need to use the handle in client code. -{% highlight haskell %} +```haskell instance Resolvable (Registry k v) where resolve = return . Just . registryPid -{% endhighlight %} +``` The [`Routable`][rtbl] typeclass provides a means to dispatch messages without having to know the implementation details behind the scenes. This provides us @@ -190,16 +190,16 @@ There is a default (and fairly efficient) instance of [`Routable`][rtbl] for all [`Resolvable`][rsbl] instances, so it is usually enough to implement the latter. An explicit implementation for our `Registry` would look like this: -{% highlight haskell %} +```haskell instance Routable (Registry k v) where sendTo reg msg = send (registryPid reg) msg unsafeSendTo reg msg = unsafeSend (registryPid reg) msg -{% endhighlight %} +``` Similar typeclasses are provided for the many occaisions when you need to link to or kill a process without knowing its `ProcessId`: -{% highlight haskell %} +```haskell class Linkable a where -- | Create a /link/ with the supplied object. linkTo :: a -> Process () @@ -207,7 +207,7 @@ class Linkable a where class Killable a where killProc :: a -> String -> Process () exitProc :: (Serializable m) => a -> m -> Process () -{% endhighlight %} +``` Again, there are default instances of both typeclasses for all [`Resolvable`][rsbl] types, so it is enough to provide just that instance for your handles. @@ -264,11 +264,11 @@ do, right up to monitoring the server for potential exit signals (so as not to deadlock the client if the server dies before replying) - all of which is handled by `awaitResponse` in the platform's `Primitives` module. -{% highlight haskell %} +```haskell syncSafeCallChan server msg = do rp <- callChan server msg awaitResponse server [ matchChan rp (return . Right) ] -{% endhighlight %} +``` This might sound like a vast improvement on the usual combination of a client API that uses `call` and a corresponding `handleCall` in the process definition, @@ -280,7 +280,7 @@ so on. None of these features will work with the corollary family of leave as a question for the reader to determine. The following example demonstrates the use of reply channels: -{% highlight haskell %} +```haskell -- two versions of the same handler, one for calls, one for typed (reply) channels data State @@ -304,7 +304,7 @@ callHandler = handleCall $ \state Input -> reply Output state chanHandler :: Dispatcher State chanHandler = handleRpcChan $ \state port Input -> replyChan port Output >> continue state -{% endhighlight %} +``` ------ > ![Info: ][info] Using typed channels for replies is both flexible and efficient. @@ -361,7 +361,7 @@ to the calling process, at least to some extent. For this example, we'll examine [`Mailbox`][mailbox] module, since this combines a fire-and-forget control channel with an opaque server handle. -{% highlight haskell %} +```haskell -- our handle is fairly simple data Mailbox = Mailbox { pid :: !ProcessId , cchan :: !(ControlPort ControlMessage) @@ -424,7 +424,7 @@ processDefinition pid tc cc = do , handleRaw handleRawInputs ] , unhandledMessagePolicy = DeadLetter pid } :: Process (ProcessDefinition State) -{% endhighlight %} +``` Since the rest of the mailbox initialisation code is quite complex, we'll leave it there for now. The important details to take away are the use of `chanServe` @@ -448,7 +448,7 @@ since _that_ API only supports a single control channel - the original purpose b the control channel concept - and instead, we'll create the process loop ourselves, using the exported low level `recvLoop` function. -{% highlight haskell %} +```haskell type NumRequests = Int @@ -547,7 +547,7 @@ handleStats :: NumRequests -> StatsRequest -> Process (ProcessAction State) handleStats count (StatsReq replyTo) = do replyChan replyTo count continue count -{% endhighlight %} +``` Although not very useful, this is a working example. Note that the client must deal with a `ControlPort` and not the complete `ControlChannel` itself. Also diff --git a/tutorials/tutorial-NT2.md b/tutorials/tutorial-NT2.md index fabcd40..002b71f 100644 --- a/tutorials/tutorial-NT2.md +++ b/tutorials/tutorial-NT2.md @@ -34,20 +34,20 @@ echoed by the server back to the client. Here is what it will look like. We can start the server on one host: -{% highlight bash %} +```bash # ./tutorial-server 192.168.1.108 8080 Echo server started at "192.168.1.108:8080:0" -{% endhighlight %} +``` then start the client on another. The client opens a connection to the server, sends "Hello world", and prints all the `Events` it receives: -{% highlight bash %} +```bash # ./tutorial-client 192.168.1.109 8080 192.168.1.108:8080:0 ConnectionOpened 1024 ReliableOrdered "192.168.1.108:8080:0" Received 1024 ["Hello world"] ConnectionClosed 1024 -{% endhighlight %} +``` The client receives three `Event`s: @@ -66,21 +66,21 @@ We will start with the client ([tutorial-client.hs](https://github.com/haskell-distributed/distributed-process/blob/master/doc/tutorial/tutorial-client.hs)), because it is simpler. We first need a bunch of imports: -{% highlight haskell %} +```haskell import Network.Transport import Network.Transport.TCP (createTransport, defaultTCPParameters) import Network.Socket.Internal (withSocketsDo) import System.Environment import Data.ByteString.Char8 import Control.Monad -{% endhighlight %} +``` The client will consist of a single main function. [withSocketsDo](http://hackage.haskell.org/package/network-2.6.2.1/docs/Network-Socket-Internal.html#v:withSocketsDo) may be needed for Windows platform with old versions of network library. For compatibility with older versions on Windows, it is good practice to always call withSocketsDo (it's very cheap). -{% highlight haskell %} +```haskell main :: IO () main = withSocketsDo $ do -{% endhighlight %} +``` When we start the client we expect three command line arguments. Since the client will itself be a network endpoint, we need to know the IP @@ -88,40 +88,40 @@ address and port number to use for the client. Moreover, we need to know the endpoint address of the server (the server will print this address to the console when it is started): -{% highlight haskell %} +```haskell [host, port, serverAddr] <- getArgs -{% endhighlight %} +``` Next we need to initialize the Network.Transport layer using `createTransport` from `Network.Transport.TCP` (in this tutorial we will use the TCP instance of `Network.Transport`). The type of `createTransport` is: -{% highlight haskell %} +```haskell createTransport :: N.HostName -> N.ServiceName -> IO (Either IOException Transport) -{% endhighlight %} +``` (where `N` is an alias for `Network.Socket`). For the sake of this tutorial we are going to ignore all error handling, so we are going to assume it will return a `Right` transport: -{% highlight haskell %} +```haskell Right transport <- createTransport host port -{% endhighlight %} +``` Next we need to create an EndPoint for the client. Again, we are going to ignore errors: -{% highlight haskell %} +```haskell Right endpoint <- newEndPoint transport -{% endhighlight %} +``` Now that we have an endpoint we can connect to the server, after we convert the `String` we got from `getArgs` to an `EndPointAddress`: -{% highlight haskell %} +```haskell let addr = EndPointAddress (pack serverAddr) Right conn <- connect endpoint addr ReliableOrdered defaultConnectHints -{% endhighlight %} +``` `ReliableOrdered` means that the connection will be reliable (no messages will be lost) and ordered (messages will arrive in order). For the case of the TCP transport @@ -130,33 +130,33 @@ not be true for other transports. Sending on our new connection is very easy: -{% highlight haskell %} +```haskell send conn [pack "Hello world"] -{% endhighlight %} +``` (`send` takes as argument an array of `ByteString`s). Finally, we can close the connection: -{% highlight haskell %} +```haskell close conn -{% endhighlight %} +``` Function `receive` can be used to get the next event from an endpoint. To print the first three events, we can do -{% highlight haskell %} +```haskell replicateM_ 3 $ receive endpoint >>= print -{% endhighlight %} +``` Since we're not expecting more than 3 events, we can now close the transport. -{% highlight haskell %} +```haskell closeTransport transport -{% endhighlight %} +``` That's it! Here is the entire client again: -{% highlight haskell %} +```haskell main :: IO () main = withSocketsDo $ do [host, port, serverAddr] <- getArgs @@ -171,7 +171,7 @@ main = withSocketsDo $ do replicateM_ 3 $ receive endpoint >>= print closeTransport transport -{% endhighlight %} +``` ### Writing the server @@ -179,7 +179,7 @@ The server ([tutorial-server.hs](https://github.com/haskell-distributed/distribu is slightly more complicated, but only slightly. As with the client, we start with a bunch of imports: -{% highlight haskell %} +```haskell import Network.Transport import Network.Transport.TCP (createTransport, defaultTCPParameters) import Network.Socket.Internal (withSocketsDo) @@ -187,11 +187,11 @@ import Control.Concurrent import Data.Map import Control.Exception import System.Environment -{% endhighlight %} +``` We will write the main function first: -{% highlight haskell %} +```haskell main :: IO () main = withSocketsDo $ do [host, port] <- getArgs @@ -201,7 +201,7 @@ main = withSocketsDo $ do forkIO $ echoServer endpoint serverDone putStrLn $ "Echo server started at " ++ show (address endpoint) readMVar serverDone `onCtrlC` closeTransport transport -{% endhighlight %} +``` This is very similar to the `main` function for the client. We get the hostname and port number that the server should use and create a transport @@ -218,14 +218,14 @@ our connection to them. `Event` is defined in `Network.Transport` as -{% highlight haskell %} +```haskell data Event = Received ConnectionId [ByteString] | ConnectionClosed ConnectionId | ConnectionOpened ConnectionId Reliability EndPointAddress | EndPointClosed ... -{% endhighlight %} +``` (there are few other events, which we are going to ignore). `ConnectionId`s help us distinguish messages sent on one connection from messages sent on another. In @@ -240,7 +240,7 @@ Finally, when we receive the `EndPointClosed` message we signal to the main thread that we are doing and terminate. We will receive this message when the main thread calls `closeTransport` (that is, when the user presses Control-C). -{% highlight haskell %} +```haskell echoServer :: EndPoint -> MVar () -> IO () echoServer endpoint serverDone = go empty where @@ -268,20 +268,20 @@ echoServer endpoint serverDone = go empty EndPointClosed -> do putStrLn "Echo server exiting" putMVar serverDone () -{% endhighlight %} +``` This implements almost exactly what we described above. The only complication is that we want to avoid blocking the receive queue; so for every message that comes in we spawn a new thread to deal with it. Since is therefore possible that we receive the `Received` event before an outgoing connection has been established, we map connection IDs to MVars containing connections. Finally, we need to define `onCtrlC`; `p onCtrlC q` will run `p`; if this is interrupted by Control-C we run `q` and then try again: -{% highlight haskell %} +```haskell onCtrlC :: IO a -> IO () -> IO a p `onCtrlC` q = catchJust isUserInterrupt p (const $ q >> p `onCtrlC` q) where isUserInterrupt :: AsyncException -> Maybe () isUserInterrupt UserInterrupt = Just () isUserInterrupt _ = Nothing -{% endhighlight %} +``` ### Conclusion diff --git a/wiki.md b/wiki.md index 675dede..66631f7 100644 --- a/wiki.md +++ b/wiki.md @@ -24,9 +24,9 @@ There is a makefile in the root directory which will create a wiki page for you (in the wiki directory) and populate the front matter for you. Calling the makefile is pretty easy. -{% highlight bash %} +```bash make wikipage NAME= -{% endhighlight %} +``` [1]: https://github.com/mojombo/jekyll [2]: https://github.com/haskell-distributed/haskell-distributed.github.com diff --git a/wiki/contributing.md b/wiki/contributing.md index 6612207..9511dde 100644 --- a/wiki/contributing.md +++ b/wiki/contributing.md @@ -69,7 +69,7 @@ make your changes in a local branch. Before submitting your pull request, fetch and rebase any changes to the upstream source branch and merge these into your local branch. For example: -{% highlight bash %} +```bash ## on your local repository, create a branch to work in $ git checkout -b bugfix-issue123 @@ -89,7 +89,7 @@ $ git merge master ## make sure you resolve any merge conflicts ## and commit before sending a pull request! -{% endhighlight %} +``` ### __3. Follow the patch submission *rules of thumb*__ @@ -188,7 +188,7 @@ quite frequently and it is pain keeping the indentation consistent. The one exception to this is probably imports/exports, which we *are* a bit finicky about: -{% highlight haskell %} +```haskell import qualified Foo.Bar.Baz as Bz import Data.Binary ( Binary (..), @@ -197,7 +197,7 @@ import Data.Binary ) import Data.Blah import Data.Boom (Typeable) -{% endhighlight %} +``` We generally don't care *that much* about alignment for other things, but as always, try to follow the convention in the file you're editing @@ -216,14 +216,14 @@ Comment every top level function (particularly exported functions), and provide a type signature; use Haddock syntax in the comments. Comment every exported data type. Function example: -{% highlight haskell %} +```haskell -- | Send a message on a socket. The socket must be in a connected -- state. Returns the number of bytes sent. Applications are -- responsible for ensuring that all data has been sent. send :: Socket -- ^ Connected socket -> ByteString -- ^ Data to send -> IO Int -- ^ Bytes sent -{% endhighlight %} +``` For functions, the documentation should give enough information to apply the function without looking at the function's definition. diff --git a/wiki/networktransport.md b/wiki/networktransport.md index f6fb51a..0938482 100644 --- a/wiki/networktransport.md +++ b/wiki/networktransport.md @@ -44,7 +44,7 @@ You may also submit issues on [github][8]. For a flavour of what programming with `Network.Transport` looks like, here is a tiny self-contained example. -{% highlight haskell %} +```haskell import Network.Transport import Network.Transport.TCP (createTransport, defaultTCPParameters) import Control.Concurrent @@ -84,7 +84,7 @@ main = do conn <- takeMVar clientDone close conn takeMVar serverDone -{% endhighlight %} +``` We create a "server" and a "client" (each represented by an `EndPoint`). The server waits for `Event`s and whenever it receives a message it just prints @@ -166,14 +166,14 @@ A series of benchmarks has shown that using `Data.Serialize` is very slow (and using Blaze.ByteString not much better). This is fast: -{% highlight haskell %} +```haskell foreign import ccall unsafe "htonl" htonl :: CInt -> CInt encodeLength :: Int32 -> IO ByteString encodeLength i32 = BSI.create 4 $ \p -> pokeByteOff p 0 (htonl (fromIntegral i32)) -{% endhighlight %} +``` * We do not need to use `blaze-builder` or related; `Network.Socket.Bytestring.sendMany` uses vectored I/O. On the client side @@ -202,7 +202,7 @@ to the Transport API. We can either have this as part of the transport -{% highlight haskell %} +```haskell data Transport = Transport { ... , newMulticastGroup :: IO (Either Error MulticastGroup) @@ -213,11 +213,11 @@ We can either have this as part of the transport , multicastAddress :: MulticastAddress , deleteMulticastGroup :: IO () } -{% endhighlight %} +``` or as part of an endpoint: -{% highlight haskell %} +```haskell data Transport = Transport { newEndPoint :: IO (Either Error EndPoint) } @@ -226,7 +226,7 @@ or as part of an endpoint: ... , newMulticastGroup :: IO (Either Error MulticastGroup) } -{% endhighlight %} +``` It should probably be part of the `Transport`, as there is no real connection between an endpoint and the creation of the multigroup (however, see section @@ -239,7 +239,7 @@ endpoint wants to receive events when multicast messages are sent. We could reify a subscription: -{% highlight haskell %} +```haskell data EndPoint = EndPoint { ... , multicastSubscribe :: MulticastAddress -> IO MulticastSubscription @@ -249,17 +249,17 @@ We could reify a subscription: ... , multicastSubscriptionClose :: IO () } -{% endhighlight %} +``` but this suggests that one might have multiple subscriptions to the same group which can be distinguished, which is misleading. Probably better to have: -{% highlight haskell %} +```haskell data EndPoint = EndPoint { multicastSubscribe :: MulticastAddress -> IO () , multicastUnsubscribe :: MulticastAddress -> IO () } -{% endhighlight %} +``` #### Sending messages to a multicast group @@ -275,7 +275,7 @@ same multicast group, and if so, whether it is useful. If we decide that multiple lightweight connections to the multigroup is useful, one option might be -{% highlight haskell %} +```haskell data EndPoint = EndPoint { ... , connect :: Address -> Reliability -> IO (Either Error Connection) @@ -293,7 +293,7 @@ one option might be Receive ConnectionId [ByteString] | ConnectionClosed ConnectionId | ConnectionOpened ConnectionId ConnectionType Reliability Address -{% endhighlight %} +``` The advantage of this approach is it's consistency with the rest of the interface. The problem is that with multicast we cannot reliably send any @@ -308,7 +308,7 @@ multicast protocols, then that would fit this design). If we don't want to support multiple lightweight connections to a multicast group then a better design would be -{% highlight haskell %} +```haskell data EndPoint = EndPoint { , connect :: Address -> Reliability -> IO (Either Error Connection) , multicastSend :: MulticastAddress -> [ByteString] -> IO () @@ -317,11 +317,11 @@ group then a better design would be data Event = ... | MulticastReceive Address [ByteString] -{% endhighlight %} +``` or alternatively -{% highlight haskell %} +```haskell data EndPoint = EndPoint { ... , resolveMulticastGroup :: MulticastAddress -> IO (Either Error MulticastGroup) @@ -331,7 +331,7 @@ or alternatively , ... , send :: [ByteString] -> IO () } -{% endhighlight %} +``` If we do this however we need to make sure that newGroup is part an `EndPoint`, not the `Transport`, otherwise `send` will not know the source of the message. @@ -344,7 +344,7 @@ some point too. The above considerations lead to the following tentative proposal: -{% highlight haskell %} +```haskell data Transport = Transport { newEndPoint :: IO (Either Error EndPoint) } @@ -377,7 +377,7 @@ The above considerations lead to the following tentative proposal: , multicastUnsubscribe :: IO () , multicastClose :: IO () } -{% endhighlight %} +``` where `multicastClose` indicates to the runtime that this endpoint no longer wishes to send to this multicast group, and we can therefore deallocate the diff --git a/wiki/newdesign.md b/wiki/newdesign.md index c1ce31d..7c3499d 100644 --- a/wiki/newdesign.md +++ b/wiki/newdesign.md @@ -231,52 +231,52 @@ We start with a Transport. Creating a Transport is totally backend dependent. Mo A Transport lets us create new connections. Our current implementation provides ordinary reliable many-to-one connections, plus the multicast one-to-many connections. It does not yet provide unordered or unreliable many-to-one connections, but these will closely follow the interface for the ordinary reliable many-to-one connections. -{% highlight haskell %} +```haskell data Transport = Transport { newConnectionWith :: Hints -> IO TargetEnd , newMulticastWith :: Hints -> IO MulticastSourceEnd , deserialize :: ByteString -> Maybe Address } -{% endhighlight %} +``` We will start with ordinary connections and look at multicast later. We will return later to the meaning of the hints. We have a helper function for the common case of default hints. -{% highlight haskell %} +```haskell newConnection :: Transport -> IO TargetEnd newConnection transport = newConnectionWith transport defaultHints -{% endhighlight %} +``` The `newConnection` action creates a new connection and gives us its `TargetEnd`. The `TargetEnd` is a stateful object representing one endpoint of the connection. For the corresponding source side, instead of creating a stateful `SourceEnd` endpoint, we can take the address of any `TargetEnd`: -{% highlight haskell %} +```haskell address :: TargetEnd -> Address -{% endhighlight %} +``` The reason for getting the address of the target rather than `newConnection` just giving us a `SourceEnd` is that usually we only want to create a `SourceEnd` on remote nodes not on the local node. An `Address` represents an address of an existing endpoint. It can be serialised and copied to other nodes. On the remote node the Transport's `deserialize` function is is used to reconstruct the `Address` value. Once on the remote node, a `SourceEnd` can created that points to the `TargetEnd` identified by the `Address`. -{% highlight haskell %} +```haskell data Address = Address { connectWith :: SourceHints -> IO SourceEnd , serialize :: ByteString } -{% endhighlight %} +``` Again, ignore the hints for now. -{% highlight haskell %} +```haskell connect :: Address -> IO SourceEnd connect address = connectWith address defaultSourceHints -{% endhighlight %} +``` The `connect` action makes a stateful endpoint from the address. It is what really establishes a connection. After that the `SourceEnd` can be used to send messages which will be received at the `TargetEnd`. The `SourceEnd` and `TargetEnd` are then relatively straightforward. They are both stateful endpoint objects representing corresponding ends of an established connection. -{% highlight haskell %} +```haskell newtype SourceEnd = SourceEnd { send :: [ByteString] -> IO () } @@ -285,7 +285,7 @@ newtype TargetEnd = TargetEnd { receive :: IO [ByteString] , address :: Address } -{% endhighlight %} +``` The `SourceEnd` sports a vectored send. That is, it allows sending a message stored in a discontiguous buffer (represented as a list of ByteString chunks). The `TargetEnd` has a vectored receive, though it is not vectored in the traditional way because it is the transport not the caller that handles the buffers and decides if it will receive the incoming message into a single contiguous buffer or a discontiguous buffer. Callers must always be prepared to handle discontiguous incoming messages or pay the cost of copying into a contiguous buffer. @@ -306,7 +306,7 @@ For the multicast connections, the address, source and target ends are analogous The `newMulticast` is the other way round compared to `newConnection`: it gives us a stateful `MulticastSourceEnd` from which we can obtain the address `MulticastAddress`. -{% highlight haskell %} +```haskell newMulticast :: Transport -> IO MulticastSourceEnd newMulticast transport = newMulticastWith transport defaultHints @@ -323,7 +323,7 @@ newtype MulticastAddress = MulticastAddress newtype MulticastTargetEnd = MulticastTargetEnd { multicastReceive :: IO [ByteString] } -{% endhighlight %} +``` The multicast send has an implementation-defined upper bound on the message size which can be discovered on a per-connection basis. @@ -334,17 +334,17 @@ Creating a `Transport` object is completely backend-dependent. There is the oppo In the simplest case (e.g. a dummy in-memory transport) there might be nothing to configure: -{% highlight haskell %} +```haskell mkTransport :: IO Transport -{% endhighlight %} +``` For a TCP backend we might have: -{% highlight haskell %} +```haskell mkTransport :: TCPConfig -> IO Transport data TCPConfig = ... -{% endhighlight %} +``` This `TCPConfig` can contain arbitrary amounts of configuration data. Exactly what it contains is closely connected with how we should set per-connection parameters. @@ -359,11 +359,11 @@ With our design approach it is easy to pass backend-specific types and configura This makes it easy to use a constant set of configuration parameters for every connection. For example for our example TCP backend above we could have: -{% highlight haskell %} +```haskell data TCPConfig = TCPConfig { socketConfiguration :: SocketOptions } -{% endhighlight %} +``` This has the advantage that it gives us full access to all the options using the native types of the underlying network library (`SocketOptions` type comes from the `network` library). @@ -371,23 +371,23 @@ The drawback of this simple approach is that we cannot set different options for Allowing different connection options depending on the source and destination addresses is reasonably straightforward: -{% highlight haskell %} +```haskell data TCPConfig = TCPConfig { socketConfiguration :: Ip.Address -> Ip.Address -> SocketOptions } -{% endhighlight %} +``` We simply make the configuration be a function that returns the connection options but is allowed to vary depending on the IP addresses involved. Separately this could make use of configuration data such as a table of known nodes, perhaps passed in by a cluster job scheduler. Having options vary depending on how the connection is to be used is more tricky. If we are to continue with this approach then it relies on the transport being able to identify how a client is using (or intends to use) each connection. Our proposed solution is that when each new connection is made, the client supplies a set of "hints". These are not backend specific, they are general indications of what the client wants, or how the client intends to use the connection. The backend can then interpret these hints and transform them into the real network-specific connection options: -{% highlight haskell %} +```haskell data TCPConfig = TCPConfig { socketConfiguration :: Hints -> Ip.Address -> Ip.Address -> SocketOptions } -{% endhighlight %} +``` What exactly goes into the hints will have to be considered in consultation with networking experts and people implementing backends. In particular it might indicate if bandwidth or latency is more important (e.g. to help decide if NO_DELAY should be used), if the connection is to be heavily or lightly used (to help decide buffer size) etc. @@ -418,29 +418,29 @@ A `ProcessId` serves two purposes, one is to communicate with a process directly The main APIs involving a ProcessId are: -{% highlight haskell %} +```haskell getSelfPid :: ProcessM ProcessId send :: Serializable a => ProcessId -> a -> ProcessM () spawn :: NodeId -> Closure (ProcessM ()) -> ProcessM ProcessId -{% endhighlight %} +``` and linking and service requests: -{% highlight haskell %} +```haskell linkProcess :: ProcessId -> ProcessM () monitorProcess :: ProcessId -> ProcessId -> MonitorAction -> ProcessM () nameQuery :: NodeId -> String -> ProcessM (Maybe ProcessId) -{% endhighlight %} +``` A NodeId is used to enable us to talk to the service processes on a node. The main APIs involving a `NodeId` are: -{% highlight haskell %} +```haskell getSelfNode :: ProcessM NodeId spawn :: NodeId -> Closure (ProcessM ()) -> ProcessM ProcessId nameQuery :: NodeId -> String -> ProcessM (Maybe ProcessId) -{% endhighlight %} +``` ### NodeID and ProcessId representation @@ -454,15 +454,15 @@ So for a ProcessId we need: We define it as -{% highlight haskell %} +```haskell data ProcessId = ProcessId SourceEnd NodeId LocalProcessId -{% endhighlight %} +``` For a `NodeId` we need to be able to talk to the service processes on that node. -{% highlight haskell %} +```haskell data NodeId = NodeId SourceEnd -{% endhighlight %} +``` The single 'SourceEnd's is for talking to the basic service processes (ie the processes involved in implementing spawn and link/monitor). The service process Ids on each node are well known and need not be stored. @@ -470,17 +470,17 @@ The single 'SourceEnd's is for talking to the basic service processes (ie the pr A cloud Haskell channel `SendPort` is similar to a `ProcessId` except that we do not need the `NodeId` because we do not need to talk about the process on the other end of the port. -{% highlight haskell %} +```haskell data SendPort a = SendPort SourceEnd -{% endhighlight %} +``` ### Cloud Haskell backend initialisation and neighbour setup In the first implementation, the initialisation was done using: -{% highlight haskell %} +```haskell remoteInit :: Maybe FilePath -> (String -> ProcessM ()) -> IO () -{% endhighlight %} +``` This takes a configuration file (or uses an environment variable to find the same), an initial process, and it launches everything by reading the config, creating the local node and running the initial process. The initial process gets passed some role string obtained from the configuration file. @@ -488,11 +488,11 @@ One of the slightly tricky issues with writing a program for a cluster is how to The first cloud Haskell implementation provides: -{% highlight haskell %} +```haskell type PeerInfo = Map String [NodeId] getPeers :: ProcessM PeerInfo findPeerByRole :: PeerInfo -> String -> [NodeId] -{% endhighlight %} +``` The implementation obtains this information using magic and configuration files. @@ -511,51 +511,51 @@ So in the new design, each application selects a Cloud Haskell backend by import Exactly how this is exposed has not been finalised. Internally we have an abstraction `LocalNode` which is a context object that knows about all the locally running processes on the node. We have: -{% highlight haskell %} +```haskell newLocalNode :: Transport -> IO LocalNode runProcess :: LocalNode -> Process () -> IO () -{% endhighlight %} +``` and each backend will (at least internally) have a function something like: -{% highlight haskell %} +```haskell mkTransport :: {...config...} -> IO Transport -{% endhighlight %} +``` So the initialisation process is more or less -{% highlight haskell %} +```haskell init :: {...} -> Process () -> IO () init config initialProcess = do transport <- mkTransport config localnode <- newLocalNode transport runProcess localnode initialProcess -{% endhighlight %} +``` We could export all these things and have applications plug them together. Alternatively we might have each backend provide an initialisation that does it all in one go. For example the backend that forks multiple OS process might have an init like this: -{% highlight haskell %} +```haskell init :: Int -> ([NodeId] -> Process ()) -> IO () -{% endhighlight %} +``` It takes a number of (OS) processes to fork and the initial (CH) process gets passes a corresponding number of remote `NodeId`s. For the backend that deals with VMs in the cloud, it might have two initialisation functions, one for the controller node and one for worker nodes. -{% highlight haskell %} +```haskell initController :: ControllerConfig -> Process () -> IO () initWorker :: WorkerConfig -> IO () -{% endhighlight %} +``` Additionally it might have actions for firing up new VMs and running the program binary in worker mode on that VM: -{% highlight haskell %} +```haskell spawnVM :: VmAccount -> IO VM initOnVM :: VM -> IO NodeId shutdownVM :: VM -> IO () -{% endhighlight %} +``` For example, supposing in our application's 'main' we call the IP backend and initialise a Transport object, representing the transport backend for cloud Haskell: @@ -565,9 +565,9 @@ There are contexts where it makes sense to use more than one `Transport` in a si There are various challenges related to addressing. Assuming these can be solved, it should be considered how initialisation might be done when there are multiple transports / backends in use. We might want to have: -{% highlight haskell %} +```haskell newLocalNode :: [Transport] -> IO LocalNode -{% endhighlight %} +``` and expose it to the clients. diff --git a/wiki/newtransports.md b/wiki/newtransports.md index 69d614f..d3e0ddc 100644 --- a/wiki/newtransports.md +++ b/wiki/newtransports.md @@ -8,15 +8,15 @@ wiki: Guide On this page we describe the TCP Transport as an example for developers who wish to write their own instantiations of the Transport layer. The purpose of any such instantiation is to provide a function -{% highlight haskell %} +```haskell createTransport :: -> IO (Either Transport) -{% endhighlight %} +``` For instance, the TCP transport offers -{% highlight haskell %} +```haskell createTransport :: N.HostName -> N.ServiceName -> IO (Either IOException Transport) -{% endhighlight %} +``` This could be the only function that `Network.Transport.TCP` exports (the only reason it exports more is to provide an API for unit tests for the TCP transport, some of which work at a lower level). Your implementation will now be guided by the `Network.Transport` API. In particular, you will need to implement `newEndPoint`, which in turn will require you to implement `receive`, `connect`, etc. @@ -85,23 +85,23 @@ In the TCP transport `createTransport` needs to do some setup, `newEndPoint` bar Network.Transport API functions should not throw any exceptions, but declare explicitly in their types what errors can be returned. This means that we are very explicit about which errors can occur, and moreover map Transport-specific errors ("socket unavailable") to generic Transport errors ("insufficient resources"). A typical example is `connect` with type: -{% highlight haskell %} +```haskell connect :: EndPoint -- ^ Local endpoint -> EndPointAddress -- ^ Remote endpoint -> Reliability -- ^ Desired reliability of the connection -> IO (Either (TransportError ConnectErrorCode) Connection) -{% endhighlight %} +``` `TransportError` is defined as -{% highlight haskell %} +```haskell data TransportError error = TransportError error String deriving Typeable -{% endhighlight %} +``` and has `Show` and `Exception` instances so that application code has the option of `throw`ing returned errors. Here is a typical example of error handling in the TCP transport; it is an internal function that does the initial part of the TCP connection setup: create a new socket, and the remote endpoint ID we're interested in and our own address, and then wait for and return the response: -{% highlight haskell %} +```haskell socketToEndPoint :: EndPointAddress -- ^ Our address -> EndPointAddress -- ^ Their address -> IO (Either (TransportError ConnectErrorCode) (N.Socket, ConnectionRequestResponse)) @@ -128,13 +128,13 @@ socketToEndPoint (EndPointAddress ourAddress) theirAddress = try $ do invalidAddress = TransportError ConnectNotFound . show insufficientResources = TransportError ConnectInsufficientResources . show failed = TransportError ConnectFailed . show -{% endhighlight %} +``` Note how exceptions get mapped to `TransportErrors` using `mapExceptionID`, which is defined in `Network.Transport.Internal` as -{% highlight haskell %} +```haskell mapExceptionIO :: (Exception e1, Exception e2) => (e1 -> e2) -> IO a -> IO a mapExceptionIO f p = catch p (throw . f) -{% endhighlight %} +``` Moreover, the original exception is used as the `String` part of the `TransportError`. This means that application developers get transport-specific feedback, which is useful for debugging, not cannot take use this transport-specific information in their _code_, which would couple applications to tightly with one specific transport implementation. From 5b9870c55741f892e96dbe4afc819919f1ed685d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Nov 2025 00:23:29 +0000 Subject: [PATCH 4/4] Update inline code styling to match site color scheme Changed inline code from red accent to blue primary colors to better match the overall site design and create a more cohesive look. ## Changes ### Color Update - **Before**: Red accent (#bf616a) on light grey background - **After**: Dark blue (#4c6a8f) on light blue background ### New Styling - Background: `#e8f0f8` (light blue, complementary to site theme) - Text: `#4c6a8f` (dark blue, matches --primary-dark) - Border: `#d0dde9` (subtle blue-grey for definition) - Font weight: 500 (medium, for readability) ### CSS Variables Added new CSS custom properties for maintainability: - `--inline-code-bg`: #e8f0f8 - `--inline-code-color`: #4c6a8f - `--inline-code-border`: #d0dde9 ### Files Modified - css/custom.css: Updated inline code styling and added variables - css/syntax.css: Updated :not(pre) > code selector to match - code-test.md: Updated test page with inline code examples ### Design Rationale The new inline code styling: - Complements the primary blue (#5e81ac) used in navigation and hero - Matches the blue gradient used throughout the site - Provides excellent contrast and readability - Creates visual consistency with the Nord-inspired color palette - Distinguished from code blocks while maintaining cohesion Inline code now feels like a natural part of the site's design rather than a contrasting accent element. --- code-test.md | 11 ++++++++++- css/custom.css | 8 ++++++-- css/syntax.css | 6 ++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/code-test.md b/code-test.md index cf03aee..d7e1004 100644 --- a/code-test.md +++ b/code-test.md @@ -8,10 +8,19 @@ This page demonstrates the code highlighting styles. ## Inline Code -Here is some `inline code` that should be readable. +Here is some `inline code` that should be readable and match the site's blue color scheme. You can use inline code like `import Control.Distributed.Process` in your text. +More examples: `ProcessId`, `send`, `expect`, `newLocalNode`, `runProcess` + +The inline code now uses the primary color scheme: +- Background: Light blue (`#e8f0f8`) +- Text: Dark blue (`#4c6a8f`) - matches the primary-dark color +- Border: Subtle blue-grey (`#d0dde9`) + +This creates a cohesive look with the rest of the site, complementing the hero gradient and primary blue (`#5e81ac`) used throughout. + ## Code Blocks ### Haskell Code diff --git a/css/custom.css b/css/custom.css index 9bf71c4..01912b7 100644 --- a/css/custom.css +++ b/css/custom.css @@ -11,6 +11,9 @@ --light-text: #eceff4; --border-color: #d8dee9; --code-bg: #3b4252; + --inline-code-bg: #e8f0f8; + --inline-code-color: #4c6a8f; + --inline-code-border: #d0dde9; } /* Typography */ @@ -65,13 +68,14 @@ pre { } code { - background-color: #f5f5f5; + background-color: var(--inline-code-bg); border-radius: 3px; padding: 0.2em 0.4em; font-size: 0.9em; font-family: "Fira Code", "Monaco", "Consolas", "Courier New", monospace; - color: #bf616a; + color: var(--inline-code-color); font-weight: 500; + border: 1px solid var(--inline-code-border); } pre code { diff --git a/css/syntax.css b/css/syntax.css index 7fe873f..165ece7 100644 --- a/css/syntax.css +++ b/css/syntax.css @@ -75,11 +75,13 @@ code.sourceCode { /* Inline code (not in pre blocks) */ :not(pre) > code { - background-color: #e5e9f0; - color: #bf616a; + background-color: #e8f0f8; + color: #4c6a8f; padding: 0.2em 0.4em; border-radius: 3px; font-size: 0.9em; + border: 1px solid #d0dde9; + font-weight: 500; } /* Ensure all code blocks are styled properly */