Skip to content

Tut-powered blog #119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Oct 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ Gemfile.lock
/vendor/bundle
css/main.css
.sass-cache
target
_posts
.deploy
26 changes: 26 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,34 @@ sudo: false
language: ruby
rvm: 2.2

env:
global:
- secure: "WowDbou7Zejy22YFIxnO0enMSGtxQi++cfp0IKDv/8aK2JVR2FG4TLvLUpW2py0QWfRCyUfNfPxM/Srr4czx580mG5PUrPdYwOR4sk3/VI2SA6FNxteObBp1I0eRopHrXzXFess50ruiLBDVuc+3eM63n2O1gl2VSnW7jKdpL80="

install:
- bundle install
- curl -s https://raw.githubusercontent.com/paulp/sbt-extras/master/sbt -o sbt
- chmod +x sbt

script:
- ./sbt run
- bundle exec jekyll build

cache:
directories:
- $HOME/.ivy2/cache
- $HOME/.sbt/boot
before_cache:
- find $HOME/.ivy2 -name "ivydata-*.properties" -delete
- find $HOME/.sbt -name "*.lock" -delete

branches:
except:
- master

deploy:
- provider: script
script: ./deploy.sh
skip_cleanup: true
on:
branch: development
2 changes: 1 addition & 1 deletion _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ kramdown:
hard_wrap: false
highlighter: rouge
exclude:
["node_modules", "gulpfile.js", "package.json", "README.md", "CNAME", "vendor"]
["node_modules", "gulpfile.js", "package.json", "README.md", "CNAME", "vendor", "src", "project", "build.sbt", "posts"]
permalink: /blog/:year/:month/:day/:title.html
paginate: 10
paginate_path: "/blog/:num/"
Expand Down
31 changes: 31 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
scalaVersion := "2.11.8"

name := "typelevel-blog-tut"

scalacOptions ++= Seq(
"-deprecation",
"-feature"
)

libraryDependencies ++= Seq(
"io.get-coursier" %% "coursier" % "1.0.0-M14",
"io.get-coursier" %% "coursier-cache" % "1.0.0-M14",
"com.chuusai" %% "shapeless" % "2.3.0",
"org.yaml" % "snakeyaml" % "1.17"
)

lazy val tutInput = SettingKey[File]("tutInput")
lazy val tutOutput = SettingKey[File]("tutOutput")
lazy val tutVersion = SettingKey[String]("tutVersion")

tutInput := (baseDirectory in ThisBuild).value / "posts"
tutOutput := (baseDirectory in ThisBuild).value / "_posts"
tutVersion := "0.4.4"

watchSources ++= (tutInput.value ** "*.md").get

enablePlugins(BuildInfoPlugin)

buildInfoKeys := Seq[BuildInfoKey](tutInput, tutOutput, tutVersion)

buildInfoPackage := "org.typelevel.blog"
30 changes: 30 additions & 0 deletions deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash

set -o pipefail
set -e

GIT_EMAIL="[email protected]"
GIT_NAME="Typelevel Bot"
REMOTE_REPO="https://[email protected]/typelevel/typelevel.github.com.git"
BRANCH="master"

if [ -z "$GH_TOKEN" ]; then
echo "No GitHub access token set, not deploying"
exit 1
fi

git config --global user.name "$GIT_NAME"
git config --global user.email "$GIT_EMAIL"

git clone --depth 1 -b "$BRANCH" --no-checkout "$REMOTE_REPO" .deploy
git archive --format=tar "$TRAVIS_COMMIT" | (
cd .deploy
tar xf -
)

cp -r _posts .deploy

cd .deploy
git add -A -f
git commit -m "Update site at $(date --rfc-2822 -u)" --allow-empty
git push
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ meta:
nav: blog
author: adelbertc
pygments: true

tut:
scala: 2.11.8
binaryScala: "2.11"
dependencies:
- org.typelevel::cats-core:0.7.2
---

The common encoding of type classes in Scala relies on subtyping. This singular
Expand All @@ -16,7 +22,7 @@ fact gives us a certain cleanliness in the code, but at what cost?
Consider the following hierarchy of type classes. A similar hierarchy can be
found in both [Cats][cats] and [Scalaz 7][scalaz7].

```scala
```tut:book:silent
trait Functor[F[_]]

trait Applicative[F[_]] extends Functor[F]
Expand All @@ -34,15 +40,15 @@ us to call methods like `map`, `flatMap`, and `traverse` directly on some
`F[A]`, provided `F` has the appropriate type class instances (`Functor`,
`Monad`, and `Traverse`, respectively).

```scala
```tut:reset:book:silent
import cats._
import cats.implicits._
```

One important consequence is we can use for comprehensions in methods
parameterized over some `Monad`.

```scala
```tut:book:silent
def foo[F[_]: Monad]: F[Int] = for {
a <- Monad[F].pure(10)
b <- Monad[F].pure(20)
Expand All @@ -57,17 +63,9 @@ Or is it?
Consider a case where we want to abstract over a data type that has
both `Monad` and `Traverse`.

```scala
```tut:book:fail
// Ignore the fact we're not even using `Traverse` - we can't even call `map`!
def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].pure(10).map(identity)
// <console>:19: error: value map is not a member of type parameter F[Int]
// def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].pure(10).map(identity)
// ^
// <console>:19: error: missing argument list for method identity in object Predef
// Unapplied methods are only converted to functions when a function type is expected.
// You can make this conversion explicit by writing `identity _` or `identity(_)` instead of `identity`.
// def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].pure(10).map(identity)
// ^
```

We're already in trouble. In order to call `map` we need `F` to have a
Expand All @@ -83,26 +81,20 @@ knowing that.
This problem generalizes to anytime the compiler decides an implicit is ambiguous,
such as method calls.

```scala
```tut:book:silent
// The fact we don't actually use `Functor` here is irrelevant.
def bar[F[_]: Applicative: Functor]: F[Int] = Applicative[F].pure(10)
```

```scala
```tut:book:fail
def callBar[F[_]: Monad: Traverse]: F[Int] = bar[F]
// <console>:19: error: ambiguous implicit values:
// both value evidence$2 of type cats.Traverse[F]
// and value evidence$1 of type cats.Monad[F]
// match expected type cats.Functor[F]
// def callBar[F[_]: Monad: Traverse]: F[Int] = bar[F]
// ^
```

What do we do? For `map` it is easy enough to arbitrarily pick one
of the instances and call `map` on that. For function calls you
can thread the implicit through explicitly.

```scala
```tut:book:silent
def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].map(Monad[F].pure(10))(identity)

def callBar[F[_]: Monad: Traverse]: F[Int] = bar(Monad[F], Monad[F])
Expand All @@ -122,14 +114,11 @@ if we had three, four, five?
And the trouble doesn't end there. We asked for a `Monad` so let's try using
a for comprehension.

```scala
```tut:book:fail
def foo[F[_]: Monad: Traverse]: F[Int] = for {
a <- Monad[F].pure(10)
b <- Monad[F].pure(20)
} yield a + b
// <console>:21: error: value map is not a member of type parameter F[Int]
// b <- Monad[F].pure(20)
// ^
```

This is also broken! Because of how [for comprehensions][forcomp] desugar, a
Expand All @@ -139,7 +128,7 @@ This drastically reduces the ergonomics of doing anything monadic.
As with `map` we could call `flatMap` on `Monad` directly, but this quickly
becomes cumbersome.

```scala
```tut:book:silent
def foo[F[_]: Monad: Traverse]: F[Int] = {
val M = Monad[F]
M.flatMap(M.pure(10)) { a =>
Expand Down Expand Up @@ -171,7 +160,7 @@ an interesting alternative prototyped in [scato][scato], now making its way to
encoding completely throws out the notion of subtyping, encoding the hierarchy
via members instead.

```scala
```tut:reset:book:silent
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
Expand All @@ -195,7 +184,7 @@ danger of implicit ambiguity. However, for that very reason, having a
anyway. What we can do is use implicit conversions to re-encode the
hierarchy.

```scala
```tut:book:silent
implicit def applicativeIsFunctor[F[_]: Applicative]: Functor[F] =
implicitly[Applicative[F]].functor

Expand All @@ -205,31 +194,23 @@ implicit def traverseIsFunctor[F[_]: Traverse]: Functor[F] =

But now we're back to square one.

```scala
```tut:book:silent
// Syntax for Functor
implicit class FunctorOps[F[_], A](fa: F[A])(implicit F: Functor[F]) {
def map[B](f: A => B): F[B] = F.map(fa)(f)
}
```

```scala
```tut:book:fail
def foo[F[_]: Applicative: Traverse]: F[Int] =
implicitly[Applicative[F]].pure(10).map(identity)
// <console>:19: error: value map is not a member of type parameter F[Int]
// implicitly[Applicative[F]].pure(10).map(identity)
// ^
// <console>:19: error: missing argument list for method identity in object Predef
// Unapplied methods are only converted to functions when a function type is expected.
// You can make this conversion explicit by writing `identity _` or `identity(_)` instead of `identity`.
// implicitly[Applicative[F]].pure(10).map(identity)
// ^
```

Since both implicits have equal priority, the compiler
doesn't know which one to pick. **However**, Scala has mechanisms for
[prioritizing implicits][implicits] which solves the problem.

```scala
```tut:reset:book:silent
object Prioritized { // needed for tut, irrelevant to demonstration
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
Expand Down Expand Up @@ -289,7 +270,7 @@ sources. This can be solved by removing the hierarchy from the superclasses
(removing `Functor`'s `extends FunctorConversions0`), but comes at
the cost of needing an import at use sites to bring the implicits into scope.

```scala
```tut:reset:book:silent
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
Expand Down Expand Up @@ -322,7 +303,7 @@ trait FunctorConversions0 extends FunctorConversions1 {
object Prelude extends FunctorConversions0
```

```scala
```tut:book:silent
// Need this import to get implicit conversions in scope
import Prelude._

Expand Down Expand Up @@ -350,7 +331,7 @@ Another thing we can try is to make some compromise of the two. We can
continue to use subtyping for a blessed subset of the hierarchy, and use
members for any branching type class.

```scala
```tut:reset:book:silent
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
Expand Down
1 change: 1 addition & 0 deletions project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=0.13.12
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.6.1")
39 changes: 39 additions & 0 deletions src/main/scala/FrontMatter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.typelevel.blog

import coursier._
import coursier.util.Parse
import java.io.File
import java.net.URLClassLoader

case class Tut(scala: String, binaryScala: String, dependencies: List[String]) {

val tutResolution: Resolution = Resolution(Set(
Dependency(Module("org.tpolecat", s"tut-core_$binaryScala"), BuildInfo.tutVersion)
))

val libResolution: Resolution = Resolution(dependencies.map { dep =>
val (mod, v) = Parse.moduleVersion(dep, binaryScala).right.get
Dependency(mod, v)
}.toSet)

def invoke(file: File): Unit = {
val tutClasspath = resolve(tutResolution).get
val libClasspath = resolve(libResolution).get

val classLoader = new URLClassLoader(tutClasspath.map(_.toURI.toURL).toArray, null)
val tutClass = classLoader.loadClass("tut.TutMain")
val tutMain = tutClass.getDeclaredMethod("main", classOf[Array[String]])

val commandLine = Array(
file.toString,
BuildInfo.tutOutput.toString,
".*",
"-classpath",
libClasspath.mkString(File.pathSeparator)
)

tutMain.invoke(null, commandLine)
}
}

case class FrontMatter(tut: Tut)
13 changes: 13 additions & 0 deletions src/main/scala/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.typelevel.blog

import org.yaml.snakeyaml.Yaml

object Main extends App {

val posts = BuildInfo.tutInput.listFiles().toList.filter { file =>
file.isFile() && file.getName.endsWith(".md")
}.map(Post(_))

posts.foreach(_.process())

}
Loading