Git Product home page Git Product logo

kind-projector's Introduction

Kind Projector

Continuous Integration Maven Central

Note on maintenance

This project is only maintained for Scala 2.x. No new features are developed, but bug fix releases will still be made available. Dotty/Scala 3 has built-in type lambda syntax and kind-projector compatible syntax.

Dedication

"But I don't want to go among mad people," Alice remarked.

"Oh, you can't help that," said the Cat: "we're all mad here. I'm mad. You're mad."

"How do you know I'm mad?" said Alice.

"You must be," said the Cat, "or you wouldn't have come here."

--Lewis Carroll, "Alice's Adventures in Wonderland"

Overview

One piece of Scala syntactic noise that often trips people up is the use of type projections to implement anonymous, partially-applied types. For example:

// partially-applied type named "IntOrA"
type IntOrA[A] = Either[Int, A]

// type projection implementing the same type anonymously (without a name).
({type L[A] = Either[Int, A]})#L

Many people have wished for a better way to do this.

The goal of this plugin is to add a syntax for type lambdas. We do this by rewriting syntactically valid programs into new programs, letting us seem to add new keywords to the language. This is achieved through a compiler plugin performing an (un-typed) tree transformation.

One problem with this approach is that it changes the meaning of (potentially) valid programs. In practice this means that you must avoid defining the following identifiers:

  1. Lambda and λ
  2. *, +*, and -*
  3. Λ$
  4. α$, β$, ...

If you find yourself using lots of type lambdas, and you don't mind reserving those identifiers, then this compiler plugin is for you!

Using the plugin

Kind-projector supports Scala 2.11, 2.12, and 2.13.

Note: as of version 0.11.0 the plugin is published against the full Scala version (see #15)

To use this plugin in your own projects, add the following lines to your build.sbt file:

addCompilerPlugin("org.typelevel" % "kind-projector" % "0.13.2" cross CrossVersion.full)

Note: for multi-project builds - put addCompilerPlugin clause into settings section for each sub-project.

For maven projects, add the plugin to the configuration of the maven-scala-plugin (remember to use _2.11.12, _2.12.15, _2.13.6 etc as appropriate):

<plugin>
  <groupId>net.alchim31.maven</groupId>
  <artifactId>scala-maven-plugin</artifactId>
  ...
  <configuration>
    <compilerPlugins>
      <compilerPlugin>
        <groupId>org.typelevel</groupId>
        <artifactId>kind-projector_2.13.6</artifactId>
        <version>0.13.2</version>
      </compilerPlugin>
    </compilerPlugins>
  </configuration>
</plugin>

For mill projects, add the plugin to the scalacPluginIvyDep Note the triple colons (:::) to ensure it uses the full scala version.

override def scalacPluginIvyDeps = Agg(
  ivy"org.typelevel:::kind-projector:0.13.2"
)

That's it!

Versions of the plugin earlier than 0.10.0 were released under a different organization (org.spire-math).

Inline Syntax

The simplest syntax to use is the inline syntax. This syntax resembles Scala's use of underscores to define anonymous functions like _ + _.

Since underscore is used for existential types in Scala (and it is probably too late to change this syntax), we use * for the same purpose. We also use +* and -* to handle covariant and contravariant types parameters.

Here are a few examples:

Tuple2[*, Double]        // equivalent to: type R[A] = Tuple2[A, Double]
Either[Int, +*]          // equivalent to: type R[+A] = Either[Int, A]
Function2[-*, Long, +*]  // equivalent to: type R[-A, +B] = Function2[A, Long, B]
EitherT[*[_], Int, *]    // equivalent to: type R[F[_], B] = EitherT[F, Int, B]

As you can see, this syntax works when each type parameter in the type lambda is only used in the body once, and in the same order. For more complex type lambda expressions, you will need to use the function syntax.

Inline Underscore Syntax

Since version 0.13.0 kind-projector adds an option to use underscore symbol _ instead of * to define anonymous type lambdas. The syntax roughly follows the proposed new syntax for wildcards and placeholders for Scala 3.2+ and is designed to allow cross-compilation of libraries between Scala 2 and Scala 3 while using the new Scala 3 syntax for both versions.

To enable this mode, add -P:kind-projector:underscore-placeholders to your scalac command-line. In sbt you may do this as follows:

ThisBuild / scalacOptions += "-P:kind-projector:underscore-placeholders"

This mode is designed to be used with scalac versions 2.12.14+ and 2.13.6+, these versions add an the ability to use ? as the existential type wildcard (scala/scala#9560), allowing to repurpose the underscore without losing the ability to write existential types. It is not advised that you use this mode with older versions of scalac or without -Xsource:3 flag, since you will lose the underscore syntax entirely.

Here are a few examples:

Tuple2[_, Double]        // equivalent to: type R[A] = Tuple2[A, Double]
Either[Int, +_]          // equivalent to: type R[+A] = Either[Int, A]
Function2[-_, Long, +_]  // equivalent to: type R[-A, +B] = Function2[A, Long, B]
EitherT[_[_], Int, _]    // equivalent to: type R[F[_], B] = EitherT[F, Int, B]

Examples with -Xsource:3's ?-wildcard:

Tuple2[_, ?]        // equivalent to: type R[A] = Tuple2[A, x] forSome { type x }
Either[?, +_]          // equivalent to: type R[+A] = Either[x, A] forSome { type x }
Function2[-_, ?, +_]  // equivalent to: type R[-A, +B] = Function2[A, x, B] forSome { type x }
EitherT[_[_], ?, _]    // equivalent to: type R[F[_], B] = EitherT[F, x, B] forSome { type x }

Function Syntax

The more powerful syntax to use is the function syntax. This syntax resembles anonymous functions like x => x + 1 or (x, y) => x + y. In the case of type lambdas, we wrap the entire function type in a Lambda or λ type. Both names are equivalent: the former may be easier to type or say, and the latter is less verbose.

Here are some examples:

Lambda[A => (A, A)]              // equivalent to: type R[A] = (A, A)
Lambda[(A, B) => Either[B, A]]   // equivalent to: type R[A, B] = Either[B, A]
Lambda[A => Either[A, List[A]]]  // equivalent to: type R[A] = Either[A, List[A]]

Since types like (+A, +B) => Either[A, B] are not syntactically valid, we provide two alternate methods to specify variance when using function syntax:

  • Plus/minus: (+[A], +[B]) => Either[A, B]
  • Backticks: (`+A`, `+B`) => Either[A, B]

(Note that unlike names like *, + and - do not have to be reserved. They will only be interpreted this way when used in parameters to Lambda[...] types, which should never conflict with other usage.)

Here are some examples with variance:

λ[`-A` => Function1[A, Double]]          // equivalent to: type R[-A] = Function1[A, Double]
λ[(-[A], +[B]) => Function2[A, Int, B]]  // equivalent to: type R[-A, +B] = Function2[A, Int, B]
λ[`+A` => Either[List[A], List[A]]]      // equivalent to: type R[+A] = Either[List[A], List[A]]

The function syntax also supports higher-kinded types as type parameters. The syntax overloads the existential syntax in this case (since the type parameters to a type lambda should never contain an existential).

Here are a few examples with higher-kinded types:

Lambda[A[_] => List[A[Int]]]  // equivalent to: type R[A[_]] = List[A[Int]]
Lambda[(A, B[_]) => B[A]]     // equivalent to: type R[A, B[_]] = B[A]

Finally, variance annotations on higher-kinded sub-parameters are supported using backticks:

Lambda[`x[+_]` => Q[x, List] // equivalent to: type R[x[+_]] = Q[x, List]
Lambda[`f[-_, +_]` => B[f]   // equivalent to: type R[f[-_, +_]] = B[f]

The function syntax with backtick type parameters is the most expressive syntax kind-projector supports. The other syntaxes are easier to read at the cost of being unable to express certain (hopefully rare) type lambdas.

Type lambda gotchas

The inline syntax is the tersest and is often preferable when possible. However, there are some type lambdas which it cannot express.

For example, imagine that we have trait Functor[F[_]].

You might want to write Functor[Future[List[*]]], expecting to get something like:

type X[a] = Future[List[a]]
Functor[X]

However, * always binds at the tightest level, meaning that List[*] is interpreted as type X[a] = List[a], and that Future[List[*]] is invalid.

In these cases you should prefer the lambda syntax, which would be written as:

Functor[Lambda[a => Future[List[a]]]]

Other types which cannot be written correctly using inline syntax are:

  • Lambda[a => (a, a)] (repeated use of a).
  • Lambda[(a, b) => Either[b, a]] (reverse order of type params).
  • Lambda[(a, b) => Function1[a, Option[b]]] (similar to example).

(And of course, you can use λ[...] instead of Lambda[...] in any of these expressions.)

Under The Hood

This section shows the exact code produced for a few type lambda expressions.

Either[Int, *]
({type Λ$[β$0$] = Either[Int, β$0$]})#Λ$

Function2[-*, String, +*]
({type Λ$[-α$0$, +γ$0$] = Function2[α$0$, String, γ$0$]})#Λ$

Lambda[A => (A, A)]
({type Λ$[A] = (A, A)})#Λ$

Lambda[(`+A`, B) => Either[A, Option[B]]]
({type Λ$[+A, B] = Either[A, Option[B]]})#Λ$

Lambda[(A, B[_]) => B[A]]
({type Λ$[A, B[_]] = B[A]})#Λ$

As you can see, names like Λ$ and α$ are forbidden because they might conflict with names the plugin generates.

If you dislike these unicode names, pass -Dkp:genAsciiNames=true to scalac to use munged ASCII names. This will use L_kp in place of Λ$, X_kp0$ in place of α$, and so on.

Polymorphic lambda values

Scala does not have built-in syntax or types for anonymous function values which are polymorphic (i.e. which can be parameterized with types). To illustrate that consider both of these methods:

def firstInt(xs: List[Int]): Option[Int] = xs.headOption
def firstGeneric[A](xs: List[A]): Option[A] = xs.headOption

Having implemented these methods, we can see that the second just generalizes the first to work with any type: the function bodies are identical. We'd like to be able to rewrite each of these methods as a function value, but we can only represent the first method (firstInt) this way:

val firstInt0: List[Int] => Option[Int] = _.headOption
val firstGeneric0 <what to put here???>

(One reason to want to do this rewrite is that we might have a method like .map which we'd like to pass an anonymous function value.)

Several libraries define their own polymorphic function types, such as the following polymorphic version of Function1 (which we can use to implement firstGeneric0):

trait PolyFunction1[-F[_], +G[_]] {
  def apply[A](fa: F[A]): G[A]
}

val firstGeneric0: PolyFunction1[List, Option] =
  new PolyFunction1[List, Option] {
    def apply[A](xs: List[A]): Option[A] = xs.headOption
  }

It's nice that PolyFunction1 enables us to express polymorphic function values, but at the level of syntax it's not clear that we've saved much over defining a polymorphic method (i.e. firstGeneric).

Since 0.9.0, Kind-projector provides a value-level rewrite to fix this issue and make polymorphic functions (and other types that share their general shape) easier to work with:

val firstGeneric0 = λ[PolyFunction1[List, Option]](_.headOption)

Either λ or Lambda can be used (in a value position) to trigger this rewrite. By default, the rewrite assumes that the "target method" to define is called apply (as in the previous example), but a different method can be selected via an explicit call.

In the following example we are using the polymorphic lambda syntax to define a run method on an instance of the PF trait:

trait PF[-F[_], +G[_]] {
  def run[A](fa: F[A]): G[A]
}

val f = Lambda[PF[List, Option]].run(_.headOption)

It's possible to nest this syntax. Here's an example taken from the wild of using nested polymorphic lambdas to remove boilerplate:

// without polymorphic lambdas, as in the slide
def injectFC[F[_], G[_]](implicit I: Inject[F, G]) =
  new (FreeC[F, *] ~> FreeC[G, *]) {
    def apply[A](fa: FreeC[F, A]): FreeC[G, A] =
      fa.mapSuspension[Coyoneda[G, *]](
        new (Coyoneda[F, *] ~> Coyoneda[G, *]) {
          def apply[B](fb: Coyoneda[F, B]): Coyoneda[G, B] = fb.trans(I)
        }
      )
  }

// with polymorphic lambdas
def injectFC[F[_], G[_]](implicit I: Inject[F, G]) =
  λ[FreeC[F, *] ~> FreeC[G, *]](
    _.mapSuspension(λ[Coyoneda[F, *] ~> Coyoneda[G, *]](_.trans(I)))
  )

Kind-projector's support for type lambdas operates at the type level (in type positions), whereas this feature operates at the value level (in value positions). To avoid reserving too many names the λ and Lambda names were overloaded to do both (mirroring the relationship between types and their companion objects).

Here are some examples of expressions, along with whether the lambda symbol involved represents a type (traditional type lambda) or a value (polymorphic lambda):

// type lambda (type level)
val functor: Functor[λ[a => Either[Int, a]]] = implicitly

// polymorphic lambda (value level)
val f = λ[Vector ~> List](_.toList)

// type lambda (type level)
trait CF2 extends Contravariant[λ[a => Function2[a, a, Double]]] {
  ...
}

// polymorphic lambda (value level)
xyz.translate(λ[F ~> G](fx => fx.flatMap(g)))

One pattern you might notice is that when λ occurs immediately within [] it is referring to a type lambda (since [] signals a type application), whereas when it occurs after = or within () it usually refers to a polymorphic lambda, since those tokens usually signal a value. (The () syntax for tuple and function types is an exception to this pattern.)

The bottom line is that if you could replace a λ-expression with a type constructor, it's a type lambda, and if you could replace it with a value (e.g. new Xyz[...] { ... }) then it's a polymorphic lambda.

Polymorphic lambdas under the hood

What follows are the gory details of the polymorphic lambda rewrite.

Polymorphic lambdas are a syntactic transformation that occurs just after parsing (before name resolution or typechecking). Your code will be typechecked after the rewrite.

Written in its most explicit form, a polymorphic lambda looks like this:

λ[Op[F, G]].someMethod(<expr>)

and is rewritten into something like this:

new Op[F, G] {
  def someMethod[A](x: F[A]): G[A] = <expr>(x)
}

(The names A and x are used for clarity –- in practice unique names will be used for both.)

This rewrite requires that the following are true:

  • F and G are unary type constructors (i.e. of shape F[_] and G[_]).
  • <expr> is an expression of type Function1[_, _].
  • Op is parameterized on two unary type constructors.
  • someMethod is parametric (for any type A it takes F[A] and returns G[A]).

For example, Op might be defined like this:

trait Op[M[_], N[_]] {
  def someMethod[A](x: M[A]): N[A]
}

The entire λ-expression will be rewritten immediately after parsing (and before name resolution or typechecking). If any of these constraints are not met, then a compiler error will occur during a later phase (likely type-checking).

Here are some polymorphic lambdas along with the corresponding code after the rewrite:

val f = Lambda[NaturalTransformation[Stream, List]](_.toList)
val f = new NaturalTransformation[Stream, List] {
  def apply[A](x: Stream[A]): List[A] = x.toList
}

type Id[A] = A
val g = λ[Id ~> Option].run(x => Some(x))
val g = new (Id ~> Option) {
  def run[A](x: Id[A]): Option[A] = Some(x)
}

val h = λ[Either[Unit, *] Convert Option](_.fold(_ => None, a => Some(a)))
val h = new Convert[Either[Unit, *], Option] {
  def apply[A](x: Either[Unit, A]): Option[A] =
    x.fold(_ => None, a => Some(a))
}

// that last example also includes a type lambda.
// the full expansion would be:
val h = new Convert[({type Λ$[β$0$] = Either[Unit, β$0$]})#Λ$, Option] {
  def apply[A](x: ({type Λ$[β$0$] = Either[Unit, β$0$]})#Λ$): Option[A] =
    x.fold(_ => None, a => Some(a))
}

Unfortunately the type errors produced by invalid polymorphic lambdas are likely to be difficult to read. This is an unavoidable consequence of doing this transformation at the syntactic level.

Building the plugin

You can build kind-projector using SBT 0.13.0 or newer.

Here are some useful targets:

  • compile: compile the code
  • package: build the plugin jar
  • test: compile the test files (no tests run; compilation is the test)
  • console: launch a REPL with the plugin loaded so you can play around

You can use the plugin with scalac by specifying it on the command-line. For instance:

scalac -Xplugin:kind-projector_2.13.6-0.13.2.jar test.scala

Releasing the plugin

This project must use full cross-versioning and thus needs to be republished for each new release of Scala, but if the code doesn't change, we prefer not to ripple downstream with a version bump. Thus, we typically republish from a tag.

  1. Be sure you're on Java 8.

  2. Run ./scripts/back-publish with the tag and Scala version

    $ ./scripts/back-publish
    Usage: ./scripts/back-publish [-t <tag>] [-s <scala_version>]
    $ ./scripts/back-publish -t v0.13.2 -s 2.13.8

You can also run the above steps in CI by manually triggering the workflow in backpublish.yml. To do so, navigate to the Actions tab, select the Back-Publish workflow from the left side-bar, and click Run workflow to access a drop-down menu to run the workflow.

Screen Shot 2022-01-11 at 11 09 12

Known issues & errata

When dealing with type parameters that take covariant or contravariant type parameters, only the function syntax is supported. Huh???

Here's an example that highlights this issue:

def xyz[F[_[+_]]] = 12345
trait Q[A[+_], B[+_]]

// we can use kind-projector to adapt Q for xyz
xyz[λ[`x[+_]` => Q[x, List]] // ok

// but these don't work (although support for the second form
// could be added in a future release).
xyz[Q[*[+_], List]]          // invalid syntax
xyz[Q[*[`+_`], List]]        // unsupported

There have been suggestions for better syntax, like [A, B]Either[B, A] or [A, B] => Either[B, A] instead of Lambda[(A, B) => Either[B, A]]. Unfortunately this would actually require modifying the parser (i.e. the language itself) which is outside the scope of this project (at least, until there is an earlier compiler phase to plug into).

Others have noted that it would be nicer to be able to use _ for types the way we do for values, so that we could use Either[Int, _] to define a type lambda the way we use 3 + _ to define a function. Unfortunately, it's probably too late to modify the meaning of _, which is why we chose to use * instead.

Future Work

As of 0.5.3, kind-projector should be able to support any type lambda that can be expressed via type projections, at least using the function syntax. If you come across a type for which kind-projector lacks a syntax, please report it.

Disclaimers

Kind projector is an unusual compiler plugin in that it runs before the typer phase. This means that the rewrites and renaming we are doing are relatively fragile, and the author disclaims all warranty or liability of any kind.

(That said, there are currently no known bugs.)

If you are using kind-projector in one of your projects, please feel free to get in touch to report problems (or a lack of problems)!

Community

People are expected to follow the Scala Code of Conduct when discussing Kind-projector on GitHub or other venues.

The project's current maintainers are:

Copyright and License

All code is available to you under the MIT license, available at http://opensource.org/licenses/mit-license.php and also in the COPYING file.

Copyright Erik Osheim, 2011-2021.

kind-projector's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kind-projector's Issues

Implement a Scalafix rewrite

I think it would be useful to implement a Scalafix rewrite that rewrote code to use the improved syntax kind-projector provides.

shadowing warning with KP in nested context

Nested use of KP produces a shadowing warning. Maybe allocate new names per compilation unit or something?

scala> val x: Monad[Free[Coyoneda[Option,?],?]] = null
warning: type parameter X_kp1 defined in type L_kp shadows type X_kp1 defined in type L_kp. You may want to rename your type parameter, or possibly remove it.
...

Type used in extends not working

This should compile, but does not:

package test

trait Functor[M[_]] {
  def fmap[A, B](fa: M[A])(f: A => B): M[B]
}

class EitherRightFunctor[L] extends Functor[Either[L, ?]] {
  def fmap[A, B](fa: Either[L, A])(f: A => B): Either[L, B] =
    fa match {
      case Right(a) => Right(f(a))
      case Left(l) => Left(l)
    }
}

Move kind-projector to typelevel

Awhile ago I was contacted by @milessabin on behalf of the Scala SIP committee. There's been a desire to create a smooth upgrade path from Scala 2 to Dotty (with and without kind-projector) and they foresaw wanting to help do work.

In light of this I'm planning to move kind-projector to publish under org.typelevel so that a larger pool of people will be able to make releases. As part of that I'd also move the organization from non to typelevel and add a link to the Scala CoC as per Typelevel rules.

@TomasMikula and @SethTisue do either of you have any concerns about this? I'm planning to do this in the next few days but I realized I should check in with you both first. Feel free to 👍 this issue or respond with any concerns you have.

(And continuing thanks for helping maintain kind-projector!)

Higher order type lambdas

Hi,

is it possible to encode higher order type lambdas like λ[A => B => A] or λ[A => λ[B => A]] ?

I tried but end up with not found: type B and not found: type λ.

Thanks

Type parameter list lambda for *->*

Is there a way to do something like

def something[E, F[_] : ApplicativeError[OptionT[?[_], ?], E]](): F = ???

I tried the above and also the following but no joy yet

def something[E, A, F[_] : ApplicativeError[OptionT[?[_], A], E]](): F = ???
def something[E, F[_] : λ[α[_] => ApplicativeError[OptionT[α, ?], E]]](): F = ???
def something[E, A, F[_] : λ[α[_] => ApplicativeError[OptionT[α, A], E]]](): F = ???

There is an easy work-around for this but I was just wondering if there is a way to do it in the type parameter list instead of the implicit value parameter list.
Thanks for this amazing library by the way.

Higher-kinded placeholder with variance (e.g. F[+?[_]]) does not work.

trait ~>[-F[_], +G[_]]
def tux[T[-F[_]]] = ()
def hux[T[+G[_]]] = ()
tux[~>[-?[_], Option]]
hux[~>[Option, +?[_]]]

gives

<console>:17: error: not found: type -?
         tux[~>[-?[_], Option]]
                ^
<console>:17: error: <error> ~> Option takes no type parameters, expected: one
         tux[~>[-?[_], Option]]
             ^
<console>:20: error: not found: type +?
         hux[~>[Option, +?[_]]]
                        ^
<console>:20: error: Option ~> <error> takes no type parameters, expected: one
         hux[~>[Option, +?[_]]]

PR incoming.

Type Alias Bug

The following code compiles just fine:

trait AAAA {
  type L[T] = T => T

  def foo(): Lambda[L[String]] = ??? 
  def bar(): Lambda[String => String][Int] = ???
  def fooType: String = foo()
  def barType: Int = bar()
}

In case of foo(), String is treated as java.lang.String and in case of bar() String is treated as an identifier.
I am curious what type does Kind Projector generate for foo(). Is it ({type Λ$ = String})#Λ$?

Is this intended behavior? This definitely doesn't look right.

expanded lambdas not marked as synthetic?

I'm getting some false positives for this scalafix Disable.symbols rule

   {
    regex = {
      includes = [
        "^\\Qjava.lang.Object\\E.*$"
        "^\\Qscala.Any\\E.*$"
        "^.*\\Q#equals(Any).\\E$"
        "^.*\\Q#hashCode().\\E$"
        "^.*\\Q#toString().\\E$"
      ]
    }
    message = "prefer scalaz.{Equal, Show, Liskov, etc}"
  }

catching uses of .asInstanceOf in this code

   def interpreter[F[_]](f: Drone[F]): Ast ~> F = λ[Ast ~> F] {
    case GetBacklog() => f.getBacklog
    case GetAgents()  => f.getAgents
  }

larger context in
https://gitlab.com/fommil/drone-dynamic-agents/blob/master/src/main/scala/algebra.scala#L49-52

to reproduce, check out the repo and type sbt lint

These scalafix rules only fire for explicit code, not for synthetics. It is my guess that the λ syntax to construct a natural transformation is not marking the generated trees as synthetic and therefore scalafix is penalising them as if the user typed them, including any uses of .asInstanceOf

Assertions with -Ycheck:all

[check: kind-projector] 1 new symbols.
[check: kind-projector] package scala
Caught java.lang.AssertionError: assertion failed: <none>
semigroup.scala
java.lang.AssertionError: assertion failed: <none>
	at scala.reflect.internal.SymbolTable.throwAssertionError(SymbolTable.scala:163)
	at scala.tools.nsc.typechecker.Typers$Typer.typedPackageDef$1(Typers.scala:5260)
	at scala.tools.nsc.typechecker.Typers$Typer.typedMemberDef$1(Typers.scala:5555)
	at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:5602)
	at scala.tools.nsc.typechecker.Typers$Typer.runTyper$1(Typers.scala:5640)
	at scala.tools.nsc.typechecker.Typers$Typer.typedInternal(Typers.scala:5672)
	at scala.tools.nsc.typechecker.Typers$Typer.body$2(Typers.scala:5613)
	at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:5618)
	at scala.tools.nsc.typechecker.TreeCheckers$TreeChecker.typed(TreeCheckers.scala:256)
	at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:5699)
	at scala.tools.nsc.typechecker.TreeCheckers.$anonfun$check$1(TreeCheckers.scala:213)
	at scala.tools.nsc.typechecker.TreeCheckers.runWithUnit(TreeCheckers.scala:200)
	at scala.tools.nsc.typechecker.TreeCheckers.check(TreeCheckers.scala:211)
	at scala.tools.nsc.typechecker.TreeCheckers.$anonfun$checkTrees$3(TreeCheckers.scala:194)
	at scala.tools.nsc.typechecker.TreeCheckers.$anonfun$checkTrees$1(TreeCheckers.scala:181)
	at scala.tools.nsc.typechecker.TreeCheckers.$anonfun$checkTrees$1$adapted(TreeCheckers.scala:194)
	at scala.collection.Iterator.foreach(Iterator.scala:944)
	at scala.collection.Iterator.foreach$(Iterator.scala:944)
	at scala.collection.AbstractIterator.foreach(Iterator.scala:1432)
	at scala.tools.nsc.typechecker.TreeCheckers.checkTrees(TreeCheckers.scala:194)
	at scala.tools.nsc.Global$Run.runCheckers(Global.scala:1366)
	at scala.tools.nsc.Global$Run.compileUnitsInternal(Global.scala:1477)
	at scala.tools.nsc.Global$Run.compileUnits(Global.scala:1430)
	at scala.tools.nsc.Global$Run.compileSources(Global.scala:1423)
	at scala.tools.nsc.Global$Run.compile(Global.scala:1539)
	at xsbt.CachedCompiler0.run(CompilerInterface.scala:130)
	at xsbt.CachedCompiler0.run(CompilerInterface.scala:105)
	at xsbt.CompilerInterface.run(CompilerInterface.scala:31)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at sbt.internal.inc.AnalyzingCompiler.call(AnalyzingCompiler.scala:237)
	at sbt.internal.inc.AnalyzingCompiler.compile(AnalyzingCompiler.scala:111)
	at sbt.internal.inc.AnalyzingCompiler.compile(AnalyzingCompiler.scala:90)
	at sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$3(MixedAnalyzingCompiler.scala:83)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
	at sbt.internal.inc.MixedAnalyzingCompiler.timed(MixedAnalyzingCompiler.scala:134)
	at sbt.internal.inc.MixedAnalyzingCompiler.compileScala$1(MixedAnalyzingCompiler.scala:74)
	at sbt.internal.inc.MixedAnalyzingCompiler.compile(MixedAnalyzingCompiler.scala:117)
	at sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1(IncrementalCompilerImpl.scala:305)
	at sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1$adapted(IncrementalCompilerImpl.scala:305)
	at sbt.internal.inc.Incremental$.doCompile(Incremental.scala:101)
	at sbt.internal.inc.Incremental$.$anonfun$compile$4(Incremental.scala:82)
	at sbt.internal.inc.IncrementalCommon.recompileClasses(IncrementalCommon.scala:110)
	at sbt.internal.inc.IncrementalCommon.cycle(IncrementalCommon.scala:57)
	at sbt.internal.inc.Incremental$.$anonfun$compile$3(Incremental.scala:84)
	at sbt.internal.inc.Incremental$.manageClassfiles(Incremental.scala:129)
	at sbt.internal.inc.Incremental$.compile(Incremental.scala:75)
	at sbt.internal.inc.IncrementalCompile$.apply(Compile.scala:61)
	at sbt.internal.inc.IncrementalCompilerImpl.compileInternal(IncrementalCompilerImpl.scala:309)
	at sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileIncrementally$1(IncrementalCompilerImpl.scala:267)
	at sbt.internal.inc.IncrementalCompilerImpl.handleCompilationError(IncrementalCompilerImpl.scala:158)
	at sbt.internal.inc.IncrementalCompilerImpl.compileIncrementally(IncrementalCompilerImpl.scala:237)
	at sbt.internal.inc.IncrementalCompilerImpl.compile(IncrementalCompilerImpl.scala:68)
	at sbt.Defaults$.compileIncrementalTaskImpl(Defaults.scala:1443)
	at sbt.Defaults$.$anonfun$compileIncrementalTask$1(Defaults.scala:1417)
	at scala.Function1.$anonfun$compose$1(Function1.scala:44)
	at sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:39)
	at sbt.std.Transform$$anon$4.work(System.scala:66)
	at sbt.Execute.$anonfun$submit$2(Execute.scala:263)
	at sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:16)
	at sbt.Execute.work(Execute.scala:272)
	at sbt.Execute.$anonfun$submit$1(Execute.scala:263)
	at sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:174)
	at sbt.CompletionService$$anon$2.call(CompletionService.scala:37)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

Fix variance annotations

Currently all types created by kind-projector are invariant.

It would be great to be able to create types which are covariant or contravariant, although I'm not sure what the syntax would end up looking like.

In the worst case, I could use synthetic types like Co[A] and Contra[A] but this would only be a matter of last resort.

Consider using pcplod for testing

As per a conversation with @fommil in typelevel/general, consider adding some tests with this pcplod. If we did this, we'd want to add a new submodule (e.g. testing) that would be unpublished and would run the tests.

Benefits:

  1. We'd be verifying that the presentation compiler works with kind-projector.
  2. Provides another method of ensuring the transformations happen as expected.
  3. Policy of only adding rewrites that work in the presentation compiler.

Drawbacks:

  1. Adding dependencies on both pcplod and a testing framework.
  2. Blocks kind-projector progress based on presentation compiler status.

Overall I think it's worth exploring. Worst-case we set these tests up and later decide to disable them. Since kind-projector runs so early I think it's unlikely that we'd become majorly blocked on PC issues (but you never know).

Change ? to another symbol

It seems like Dotty will get native type lambdas. The plan there is to use F[_] as a short-hand syntax for the type lambda [X] => F[X] and F[?] to be the wildcard syntax which unfortunately is different to how the ? is used in kind-projector.
In scala/scala3#5379 there's a discussion of doing the swap incrementally in Scala 3.0 - 3.3, but imo that would lead to unnecessary churn and confusion, so I was wondering if kind-projector could preemptively replace the ? by another symbol so that until Dotty is out the ? is free to use for wildcards.
Maintainers' opinions are welcome here or in the above ticket 😃

Bring Kind-projector to LBS and TLS compilers.

kind-projector has become the de facto standard way to represent type projections in most scala code bases specially those that rely on heavy use of Higher Kinds.

Searching on GitHub yields multiple well known Scala projects that use kind-projector and the amount of success cases show it's a reliable syntax for cases where partial application of higher kinded types is necessary.

The current alternative to not using kind-projector is to use type lambdas, which is an advanced language feature that introduces a lot of noise and complexity specially for newcomers and companies adopting FP in Scala.

I believe type lambdas represent a barrier for extending FP in Scala and Kind projector provides an alternative that is sane and easier to reason about with a syntax that does not deviate from what you'd expect from Scala.

Therefore I propose we consider including kind-projector as valid syntax in Scala.

If this sound reasonable to folks interested and affected by this feature we may consider an approach that may look like:

  1. A SIP and PR to LBS and TLS.

  2. Enabling this syntax for early adopters in the Typelevel Scala compiler fork and following a similar approach as @milessabin did for SI-2712.
    As in SI-2712 those using the Typelevel compiler can enable the fix via compiler flags not requiring the compiler plugin as a dependency.

This issue is here to start the discussion to see if there is interest and what it will take to do so along with any potential issues that may arise from this language change.

A few links for reference :

Twitter conversation where this started

@retronym pointed out also the existing work that @non and @puffnfresh did here typelevel/scala#73

Tagging folks that may be interested in this issue: @non @milessabin @retronym @adriaanm

Thanks!

publish for Scala 2.13.0-M3, please?

this plugin is a prerequisite before both scalaz and pretty much the whole Typelevel ecosystem can publish

@non wrote on the similar ticket for M2:

I'm looking into this -- I think I may have to disable the pcplod tests since I don't think that is available for 2.13.0-M2.

yeah, I wouldn't wait for pcplod — https://github.com/ensime/pcplod/pull/28 was closed today without explanation

Generalize polymorphic functions to arbitrary polymorphic values

Would it be feasible to extend this approach to arbitrary polymorphic values? Essentially, providing syntax for scalaz.Forall:

/** A universally quantified value */
trait Forall[P[_]] {
  def apply[A]: P[A]
}

The polymorphic function is then just a special case:

type NaturalTransformation[F[_], G[_]] = Forall[λ[A => (F[A] => G[A])]]

The syntax could be

val f = ∀[λ[A => (List[A] => Option[A])]](_.headOption)

or in general

val f = ∀[F](<expr>)

where

  • F[_] is a unary type constructor;
  • <expr> is an expression of type Function0[F[_]].

The resulting type of f is then Forall[F].

One benefit of this is that you don't need special named traits for polymorphic functions anymore (there is scalaz.~> and cats.FunctionK, which are basically the same, but incompatible—one needs conversions between them).

The main benefit, however, is that we would have syntax for other polymorphic values, like

∀[λ[A => Lens[F[A], G[A]]]]

Then given

trait Lens[S, A] {
  def get(s: S): A
  def set(s: S, a: A): S
}
object Lens {
  def apply[S, A](get: S => A, set: (S, A) => S): Lens[S, A] = ???
}

we could define a polymorphic lens

val fstLens = ∀[λ[A => Lens[(A, Int), Id[A]]]](Lens(_._1, (ai, a) => (a, ai._2)))

where fstLens has type Forall[λ[A => Lens[(A, Int), Id[A]]]]. This would then be used like this

val x = ("Hello", 5)
fstLens[String].get(x) // "Hello"
fstLens[String].set(x, "Bye") // ("Bye", 5)

Document usage with gradle

I happen to be stuck with a build pipeline that uses gradle and its scala plugin. Is there a way to use this with gradle?

"Update: the Typelevel compiler contains a built-in syntax for type lambdas!"

...says the README. But it doesn't really, right? In fact this page seems to be rich with features which aren't in the release I've come to know as the "typelevel compiler".

Am I correct in supposing that these features existed at some point, but when you decided to limit the scope to changes which had a pull request open against scala/scala, all the other changes were sidelined?

Add means to specify name of the type parameter

Experience shows that while nine times out of ten you can get away with not being to refer to the name of the type parameter, on the tenth time you need it. Usually the cause is a situation where you are forced by scala inference to specify a type parameter, and the type A with its unknown name also must be given, because scala offers no way to specify one type argument without specifying them all.

Support type bounds

As @jedws reported in #typelevel, it would be nice to support type bounds.

Here's a possible syntax:

// you could write this
f.subst[({type λ[X >: L <: H] = Leibniz[L, H, A, X]})#λ](g)

// as either of these
f.subst[λ[`X >: L <: H` => Leibniz[L, H, A, X]]](g)
f.subst[Leibniz[L, H, A, `? >: L <: H`]](g)

Multiple-level projectors `?`

With ? you can generate a parametric type alias, projected on the position of ?. For example:

Either[Int, ?]          // equivalent to: type R[+A] = Either[Int, A]

This only works at one nesting level. If you have more than one nesting level, for instance if you want something like List[Either[Int, A]], then the ? does not work:

List[Either[Int, A]] // equivalent to List[ ({type L[x] = Either[Int, X]})#L ]

For this case, kind-projector provides the following syntax:

λ[α => List[Either[Int, α]]
Lambda[A => List[Either[Int, A]]

The first one is clear enough (after a while), but, while I am all for using and reading Unicode characters, it is not easy to remember the key-binding to write them. The second one is too verbose.

Proposal

I would like to suggest adding extra symbols to represent the notion of ?, but for two or more nesting levels of type-constructor application. For example, use two question-marks ?? to bind two levels out:

List[Either[Int, ?? ]]   // equivalent to: type R[A] = List[Either[Int, A]]

This solution would still be mostly syntactic (it only has to detect a nested type-constructor application).

Cross compile with the full Scala version

I wonder why kind-projector is cross compiled using only the major Scala version. AFAIK compiler plugins can access compiler internals, therefore are not subject to any binary compatibility guarantees between (any two) releases.

Unpositioned tree in specs2

Hi Erik,

kind-projector is breaking positions for specs2 specification macros

sbt test
[info] Loading project definition from /Users/gmasse1/Projects/bug-pos/project
[info] Set current project to bug-pos (in build file:/Users/gmasse1/Projects/bug-pos/)
[info] Compiling 1 Scala source to /Users/gmasse1/Projects/bug-pos/target/scala-2.11/test-classes...
[info] ======= Position error
[info] Unpositioned tree #931
[info]    unpositioned [L   0 P#  932] #931    [NoPosition]    Template   // Template(value <local ASpec>)
[info]       enclosing [L   4        ] #932    [35:128]        ClassDef   // ASpec extends Specification { def is = s2"""
[info]         sibling [L   0 P#  932] #931    [NoPosition]    Template   // Template(value <local ASpec>)
[info] 
[info] While validating #933
[info] [L   1        ] #933    [0:128]         PackageDef // import TTFI._
[info] 
[info] Children:
[info]   [L   1 P#  933] #52     [0:0]           Ident      // i
[info]   [L   1 P#  933] #24     [0:13]          Import     // TTFI._
[info]   [L   3 P#  933] #27     [15:34]         Import     // org.specs2._
[info]   [L   4 P#  933] #932    [35:128]        ClassDef   // ASpec extends Specification { def is = s2"""
[info] =======
scala.reflect.internal.Positions$ValidateException: Unpositioned tree #931
    at scala.reflect.internal.Positions$class.positionError$1(Positions.scala:102)
    at scala.reflect.internal.Positions$class.validate$1(Positions.scala:112)
    at scala.reflect.internal.Positions$class.validate$1(Positions.scala:142)
    at scala.reflect.internal.Positions$class.validate$1(Positions.scala:142)
    at scala.reflect.internal.Positions$class.validatePositions(Positions.scala:147)
    at scala.reflect.internal.SymbolTable.validatePositions(SymbolTable.scala:16)
    at scala.tools.nsc.typechecker.Analyzer$typerFactory$$anon$3.apply(Analyzer.scala:103)
    at scala.tools.nsc.Global$GlobalPhase$$anonfun$applyPhase$1.apply$mcV$sp(Global.scala:441)
    at scala.tools.nsc.Global$GlobalPhase.withCurrentUnit(Global.scala:432)
    at scala.tools.nsc.Global$GlobalPhase.applyPhase(Global.scala:441)
    at scala.tools.nsc.typechecker.Analyzer$typerFactory$$anon$3$$anonfun$run$1.apply(Analyzer.scala:94)
    at scala.tools.nsc.typechecker.Analyzer$typerFactory$$anon$3$$anonfun$run$1.apply(Analyzer.scala:93)
    at scala.collection.Iterator$class.foreach(Iterator.scala:750)
    at scala.collection.AbstractIterator.foreach(Iterator.scala:1202)
    at scala.tools.nsc.typechecker.Analyzer$typerFactory$$anon$3.run(Analyzer.scala:93)
    at scala.tools.nsc.Global$Run.compileUnitsInternal(Global.scala:1500)
    at scala.tools.nsc.Global$Run.compileUnits(Global.scala:1487)
    at scala.tools.nsc.Global$Run.compileSources(Global.scala:1482)
    at scala.tools.nsc.Global$Run.compile(Global.scala:1580)
    at xsbt.CachedCompiler0.run(CompilerInterface.scala:116)
    at xsbt.CachedCompiler0.run(CompilerInterface.scala:95)
    at xsbt.CompilerInterface.run(CompilerInterface.scala:26)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at sbt.compiler.AnalyzingCompiler.call(AnalyzingCompiler.scala:101)
    at sbt.compiler.AnalyzingCompiler.compile(AnalyzingCompiler.scala:47)
    at sbt.compiler.AnalyzingCompiler.compile(AnalyzingCompiler.scala:41)
    at sbt.compiler.AggressiveCompile$$anonfun$3$$anonfun$compileScala$1$1.apply$mcV$sp(AggressiveCompile.scala:97)
    at sbt.compiler.AggressiveCompile$$anonfun$3$$anonfun$compileScala$1$1.apply(AggressiveCompile.scala:97)
    at sbt.compiler.AggressiveCompile$$anonfun$3$$anonfun$compileScala$1$1.apply(AggressiveCompile.scala:97)
    at sbt.compiler.AggressiveCompile.sbt$compiler$AggressiveCompile$$timed(AggressiveCompile.scala:167)
    at sbt.compiler.AggressiveCompile$$anonfun$3.compileScala$1(AggressiveCompile.scala:96)
    at sbt.compiler.AggressiveCompile$$anonfun$3.apply(AggressiveCompile.scala:144)
    at sbt.compiler.AggressiveCompile$$anonfun$3.apply(AggressiveCompile.scala:86)
    at sbt.inc.IncrementalCompile$$anonfun$doCompile$1.apply(Compile.scala:38)
    at sbt.inc.IncrementalCompile$$anonfun$doCompile$1.apply(Compile.scala:36)
    at sbt.inc.IncrementalCommon.cycle(IncrementalCommon.scala:31)
    at sbt.inc.Incremental$$anonfun$1.apply(Incremental.scala:39)
    at sbt.inc.Incremental$$anonfun$1.apply(Incremental.scala:38)
    at sbt.inc.Incremental$.manageClassfiles(Incremental.scala:66)
    at sbt.inc.Incremental$.compile(Incremental.scala:38)
    at sbt.inc.IncrementalCompile$.apply(Compile.scala:26)
    at sbt.compiler.AggressiveCompile.compile2(AggressiveCompile.scala:158)
    at sbt.compiler.AggressiveCompile.compile1(AggressiveCompile.scala:70)
    at sbt.compiler.AggressiveCompile.apply(AggressiveCompile.scala:45)
    at sbt.Compiler$.apply(Compiler.scala:101)
    at sbt.Compiler$.apply(Compiler.scala:87)
    at sbt.Defaults$.sbt$Defaults$$compileTaskImpl(Defaults.scala:789)
    at sbt.Defaults$$anonfun$compileTask$1.apply(Defaults.scala:781)
    at sbt.Defaults$$anonfun$compileTask$1.apply(Defaults.scala:781)
    at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47)
    at sbt.$tilde$greater$$anonfun$$u2219$1.apply(TypeFunctions.scala:40)
    at sbt.std.Transform$$anon$4.work(System.scala:63)
    at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:226)
    at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:226)
    at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:17)
    at sbt.Execute.work(Execute.scala:235)
    at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:226)
    at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:226)
    at sbt.ConcurrentRestrictions$$anon$4$$anonfun$1.apply(ConcurrentRestrictions.scala:159)
    at sbt.CompletionService$$anon$2.call(CompletionService.scala:28)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
[error] (test:compile) scala.reflect.internal.Positions$ValidateException: Unpositioned tree #931
[error] Total time: 2 s, completed 28-Jan-2015 11:34:09 AM

comment out https://github.com/MasseGuillaume/kind-projector-specs2-bug/blob/e2517440ee29c6fb47125fd8c2f677db6ee726e4/build.sbt#L9

sbt test
[info] Loading project definition from /Users/gmasse1/Projects/bug-pos/project
[info] Set current project to bug-pos (in build file:/Users/gmasse1/Projects/bug-pos/)
[info] Updating {file:/Users/gmasse1/Projects/bug-pos/}bug-pos...
[info] Resolving jline#jline;2.12 ...
[info] Done updating.
[info] Compiling 1 Scala source to /Users/gmasse1/Projects/bug-pos/target/scala-2.11/test-classes...
[info] ASpec
[info] * wip PENDING
[info] 
[info] Total for specification ASpec
[info] Finished in 12 ms
[info] 1 example, 0 failure, 0 error, 1 pending
[info]  
[info] Passed: Total 0, Failed 0, Errors 0, Passed 0, Pending 1
[success] Total time: 4 s, completed 28-Jan-2015 11:34:59 AM

Rank3 Type Projection

I've been playing around with rank3 constructors and inferring for ?[_[_]] seems to not work with the plugin currently. Or at least at my understanding of it.

Currently I need

// Constructor
final case class EitherR3[F[_[_]], G[_[_]], H[_]](run: Either[F[H], G[H]])

// Type Project Required
type EitherR3Partial[F[_[_]], G[_[_]]] = EitherR3[F, G, H]
new typeclasses.BifunctorR3[EitherR3Partial]{
 def bimapR3[M[_[_]], N[_[_]], Y[_[_]], Z[_[_]]](fa: EitherR3Partial[M, N])(f: M ~~> Y, g: N ~~> Z): EitherR3Partial[Y, Z] =
  fa.bimapR3(f, g)
}

// What I would like
BifunctorR3[EitherR3[?[_[_]], ?[_[_]]], H]

Does this seem doable/reasonable?

Support parameterized placeholder syntax

Here's an example:

trait EitherT[F[_], A, B]
def hoist[K[_[_], _]] = 999
hoist[Lambda[(a[_], b) => EitherT[a, String, b]]]

It would be great to be able to say:

hoist[EitherT[?[_], String, ?]]

We should try to add support for this.

Multiple argument support for poly lambdas

Ideally, the poly λ value syntax could support multiple arguments, so instead of this -

implicit val functorOption: Functor[Option] = new Functor[Option] {
  override def map[A, B](f: A => B)(fa: Option[A]): Option[B] = fa.map(f)
}

we could write this -

implicit val functorOption: Functor[Option] = λ[Functor[Option]](f => fa => fa.map(f))

It would be even better if type inference could figure out the type arg to λ, but I have a feeling that's a baked in limitation at the moment.

implicit val functorOption: Functor[Option] = λ(f => fa => fa.map(f))

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.