Irgendwann stolpere ich in die halb mysteriöse Notation von
def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..}
in Scala-Blog-Posts, die eine Handwelle "Wir haben diesen Typ-Lambda-Trick verwendet" geben.
Obwohl ich eine gewisse Intuition darüber habe (wir erhalten einen anonymen Typparameter, A
ohne die Definition damit verschmutzen zu müssen?), Habe ich keine eindeutige Quelle gefunden, die beschreibt, was der Typ-Lambda-Trick ist und welche Vorteile er hat. Ist es nur syntaktischer Zucker oder eröffnet es neue Dimensionen?
Antworten:
Typ Lambdas sind ziemlich wichtig, wenn Sie mit höherwertigen Typen arbeiten.
Betrachten Sie ein einfaches Beispiel für die Definition einer Monade für die richtige Projektion von Entweder [A, B]. Die Monaden-Typklasse sieht folgendermaßen aus:
Jetzt ist entweder ein Typkonstruktor mit zwei Argumenten, aber um Monad zu implementieren, müssen Sie ihm einen Typkonstruktor mit einem Argument geben. Die Lösung hierfür besteht darin, einen Lambda-Typ zu verwenden:
Dies ist ein Beispiel für das Currying im Typsystem. Sie haben den Typ "Entweder" festgelegt. Wenn Sie also eine Instanz von "Entweder" erstellen möchten, müssen Sie einen der Typen angeben. Der andere wird natürlich zu dem Zeitpunkt geliefert, an dem Sie point oder bind aufrufen.
Der Typ-Lambda-Trick nutzt die Tatsache aus, dass ein leerer Block an einer Typposition einen anonymen Strukturtyp erstellt. Wir verwenden dann die # -Syntax, um ein Typmitglied zu erhalten.
In einigen Fällen benötigen Sie möglicherweise anspruchsvollere Lambdas, die nur schwer inline geschrieben werden können. Hier ist ein Beispiel aus meinem Code von heute:
Diese Klasse existiert ausschließlich, damit ich einen Namen wie FG [F, G] #IterateeM verwenden kann, um auf den Typ der IterateeT-Monade zu verweisen, die auf eine Transformatorversion einer zweiten Monade spezialisiert ist, die auf eine dritte Monade spezialisiert ist. Wenn Sie anfangen zu stapeln, werden diese Arten von Konstrukten sehr notwendig. Ich instanziiere natürlich nie eine FG; Es ist nur ein Hack, mit dem ich ausdrücken kann, was ich im Typensystem will.
quelle
bind
Methode für IhreEitherMonad
Klasse definieren. :-) Abgesehen davon, wenn ich Adriaan hier für eine Sekunde kanalisieren darf, verwenden Sie in diesem Beispiel keine höherwertigen Typen. Du bist dabeiFG
, aber nicht dabeiEitherMonad
. Sie verwenden vielmehr Typkonstruktoren , die kind haben* => *
. Diese Art ist von Ordnung 1, die nicht "höher" ist.*
Ordnung 1, aber auf jeden Fall hat Monad Art(* => *) => *
. Sie werden auch feststellen, dass ich "die richtige Projektion vonEither[A, B]
" angegeben habe - die Implementierung ist trivial (aber eine gute Übung, wenn Sie es noch nicht getan haben!)*=>*
höher zu rufen, wird durch die Analogie gerechtfertigt, dass wir keine gewöhnliche Funktion (die Nichtfunktionen Nichtfunktionen zuordnet, dh einfache Werte einfachen Werten zuordnen) Funktion höherer Ordnung rechtfertigen.Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
Die Vorteile sind genau die gleichen wie die, die durch anonyme Funktionen gewährt werden.
Eine beispielhafte Verwendung mit Scalaz 7. Wir möchten eine verwenden
Functor
, die eine Funktion über das zweite Element in a abbilden kannTuple2
.Scalaz bietet einige implizite Konvertierungen, auf die das Typargument schließen kann. Daher
Functor
vermeiden wir es oft, diese vollständig zu schreiben. Die vorherige Zeile kann wie folgt umgeschrieben werden:Wenn Sie IntelliJ verwenden, können Sie Einstellungen, Codestil, Scala, Falten und Typ Lambdas aktivieren. Dies verbirgt dann die muffigen Teile der Syntax und präsentiert die schmackhafteren:
Eine zukünftige Version von Scala unterstützt eine solche Syntax möglicherweise direkt.
quelle
(1, 2).map(a => a + 1)
in REPL einen Fehler erhalten : `<Konsole>: 11: Fehler: Wertzuordnung ist kein Mitglied von (Int, Int) (1, 2) .map (a => a + 1) ^`Um die Dinge in einen Zusammenhang zu bringen: Diese Antwort wurde ursprünglich in einem anderen Thread veröffentlicht. Sie sehen es hier, weil die beiden Threads zusammengeführt wurden. Die Fragestellung in diesem Thread lautete wie folgt:
Antworten:
Der eine Unterstrich in den Feldern danach
P
impliziert, dass es sich um einen Typkonstruktor handelt, der einen Typ verwendet und einen anderen Typ zurückgibt. Beispiele für Typkonstruktoren dieser Art:List
,Option
.Geben Sie
List
einInt
, einen konkreten Typ, und es gibt Ihnen einenList[Int]
weiteren konkreten Typ. GibList
einString
und es gibt dirList[String]
. Etc.Also
List
,Option
kann in Betracht gezogen werden Typ - Level - Funktionen von arity sein 1. Formal wir sagen, sie haben eine Art* -> *
. Das Sternchen kennzeichnet einen Typ.Jetzt
Tuple2[_, _]
ist ein Typkonstruktor mit kind,(*, *) -> *
dh Sie müssen ihm zwei Typen geben, um einen neuen Typ zu erhalten.Da ihre Signaturen nicht übereinstimmen, können Sie nicht ersetzen
Tuple2
fürP
. Was Sie tun müssen, ist teilweiseTuple2
auf eines seiner Argumente anzuwenden , wodurch wir einen Typkonstruktor mit kind erhalten* -> *
, und wir können ihn ersetzenP
.Leider hat Scala keine spezielle Syntax für die teilweise Anwendung von Typkonstruktoren, weshalb wir auf die Monstrosität zurückgreifen müssen, die als Typ Lambdas bezeichnet wird. (Was Sie in Ihrem Beispiel haben.) Sie werden so genannt, weil sie analog zu Lambda-Ausdrücken sind, die auf Wertebene existieren.
Das folgende Beispiel könnte helfen:
Bearbeiten:
Weitere Parallelen auf Wertebene und Typenebene.
In dem von Ihnen vorgestellten Fall ist der Typparameter
R
für die Funktion lokalTuple2Pure
und kann daher nicht einfach definiert werdentype PartialTuple2[A] = Tuple2[R, A]
, da es einfach keinen Ort gibt, an dem Sie dieses Synonym einfügen können.Um mit einem solchen Fall umzugehen, verwende ich den folgenden Trick, bei dem Typelemente verwendet werden. (Hoffentlich ist das Beispiel selbsterklärend.)
quelle
type World[M[_]] = M[Int]
Ich denke, dass alles, was wirA
inX[A]
dasimplicitly[X[A] =:= Foo[String,Int]]
eingeben, immer wahr ist.quelle