函数单子类型参数

31 浏览
0 Comments

函数单子类型参数

有时候我会偶然间遇到Scala博客文章中的半神秘标记\n

def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..} 

\n,它被称为\"我们使用了类型λ技巧\"。虽然我对此有一些直觉(我们在不污染定义的情况下获得了一个匿名类型参数A?),但我没有找到明确的来源来描述类型λ技巧是什么,它有什么好处。它只是一种语法糖,还是它开启了一些新的维度?

0
0 Comments

问题:为什么会出现(Function monad type parameter)这个问题以及如何解决?

在这个问题中,我们看到了一个类型定义:trait Pure[P[_]] { def pure[A](a: => A): P[A] }。在方括号中的下划线表示它是一个类型构造器,接受一个类型并返回另一个类型。例如,ListOption就是这种类型构造器的例子。给List一个Int,一个具体的类型,它会返回List[Int],另一个具体的类型。给List一个String,它会返回List[String]。等等。

因此,ListOption可以被视为一个接受一个类型并返回另一个类型的类型级函数。我们可以形式化地说,它们的kind是* -> *,其中星号表示一个类型。

然而,Tuple2[_, _]是一个接受两个类型并返回一个新类型的类型构造器。因为它们的签名不匹配,所以不能将Tuple2替换为P。我们需要做的是对Tuple2的一个参数进行部分应用,这将给我们一个kind为* -> *的类型构造器,然后我们可以将它替换为P

不幸的是,Scala没有专门的语法来对类型构造器进行部分应用,所以我们不得不使用称为"类型lambda"的怪兽。它们之所以被称为"lambda",是因为它们类似于存在于值级的lambda表达式。

为了解决这个问题,我们可以使用类型成员的技巧,例如:type Partial2[F[_, _], A] = { type Get[B] = F[A, B] }。这样我们就可以定义一个kind为* -> *的类型构造器,并将其用作P的替代。

我们可以通过使用类型lambda和类型成员来解决(Function monad type parameter)这个问题。

0
0 Comments

(Function monad type parameter)这个问题的出现的原因是想要在一个Tuple2的第二个元素上应用一个函数,并且希望使用Scala的Scalaz库中的Functor来实现这个功能。然而,由于Tuple2的类型参数是固定的,因此无法直接使用Functor。解决方法是使用类型投影和类型参数重定义来实现这个功能。

在给出的示例中,通过定义一个类型别名IntTuple来表示一个具有Int类型作为第一个元素的Tuple2。然后使用Functor[IntTuple]来将一个函数应用于IntTuple的第二个元素。使用类型投影和类型参数重定义,可以通过Functor[(Int, a)]来实现相同的功能。然而,为了简化代码,可以使用Scalaz提供的隐式转换来推断Functor的类型参数,从而避免重复写出类型投影和类型参数重定义。

最后,通过启用IntelliJ的代码折叠功能和Scala插件,可以隐藏类型投影和类型参数重定义的部分,使代码更加简洁可读。

需要注意的是,上述示例中的最后一行代码可能缺少了lambda表达式,应该为`(1, 2).map(a => a + 1)`。

总之,通过使用类型投影和类型参数重定义,结合Scalaz库中的Functor,可以实现在Tuple2的第二个元素上应用函数的功能。而通过启用代码折叠功能和Scala插件,可以使代码更加简洁可读。希望未来的Scala版本能够直接支持这样的语法。

0
0 Comments

在使用高阶类型时,类型lambda非常重要。

考虑一个简单的例子,定义一个用于Either[A, B]的右投影的monad。monad类型类如下所示:

trait Monad[M[_]] {
  def point[A](a: A): M[A]
  def bind[A, B](m: M[A])(f: A => M[B]): M[B]
}

现在,Either是一个具有两个参数的类型构造函数,但是为了实现Monad,您需要给它一个具有一个参数的类型构造函数。解决这个问题的方法是使用类型lambda:

class EitherMonad[A] extends Monad[({type λ[α] = Either[A, α]})#λ] {
  def point[B](b: B): Either[A, B]
  def bind[B, C](m: Either[A, B])(f: B => Either[A, C]): Either[A, C]
}

这是类型系统中柯里化的一个示例-您已经对Either的类型进行了柯里化,这样当您想要创建EitherMonad的实例时,必须指定其中一个类型;当然,另一个类型在调用point或bind时提供。

类型lambda技巧利用了类型位置中的空块创建匿名结构类型的事实。然后,我们使用#语法来获取类型成员。

在某些情况下,您可能需要更复杂的类型lambda,但是内联编写这些类型lambda非常麻烦。以下是我今天代码中的一个示例:

// types X and E are defined in an enclosing scope
private[iteratee] class FG[F[_[_], _], G[_]] {
  type FGA[A] = F[G, A]
  type IterateeM[A] = IterateeT[X, E, FGA, A] 
}

这个类的存在仅仅是为了让我能够使用类似FG[F, G]#IterateeM这样的名称来引用专门用于某个转换器版本的第二个monad的IterateeT monad的类型,该转换器版本专门用于某个第三个monad。当您开始叠加时,这些构造变得非常必要。当然,我从不实例化FG;它只是一个让我在类型系统中表达我想要的内容的技巧。

有趣的是,Haskell不直接支持类型级lambda,尽管一些新类型的技巧(例如TypeCompose库)有办法绕过这一点。

我很想看到您如何为EitherMonad类定义bind方法。除此之外,如果我可以引用Adriaan的话,您在那个示例中没有使用高阶类型。在FG中使用了高阶类型,但在EitherMonad中没有。而是使用了类型构造函数,其种类为* => *。这种种类是一阶的,不是"higher"。

我认为类型*是一阶的,但无论如何,Monad的种类是(* => *) => *。此外,您会注意到我指定了"Either[A, B]的右投影" - 实现非常简单(但如果您以前没有这样做过,这是一个很好的练习!)

我猜Daniel的观点,并不将* => *称为"higher",这是有道理的,因为我们不将将非函数(将非函数映射到非函数,换句话说,将普通值映射到普通值)称为高阶函数。

Pierce的TAPL书,第442页:具有类似(*=>*)=>*的种类的类型表达式称为higher-order typeoperators。

Scala 3引入了更简单的类型lambda语法。使用新的语法,上面的EitherMonad定义将变为

EitherMonad[A] extends Monad[[α] =>> Either[A, α]]

0