Local derive in custom generic instance
As an exception to the rule that generic derives are only allowed on the top level, since recently we can also derive in instances:
instance == T
where
(==) x y = trace_n "eq" (eq x y)
where
derive eq gEq
gEq{|T|} x y = x == y
which is useful to use a slightly customised version of the derived function, without deriving it on the top level, as is done to add a trace statement above.
In this case I made use of the fact that there is a class ==
with the same type and semantics as gEq
. For the case that there is no matching class, would it make sense to also allow local derives in custom instances of generic functions?
gEq{|T|} x y = trace_n "eq" (eq x y)
where
derive eq gEq
Apologies if we discussed this already and there was a reason to not add it, or it simply has not been implemented yet.
In your email of July 16th (see below) you suggested that you can implement this without classes by adding a second generic function, but this seems cumbersome if it can be avoided.
For reference I will quote your two emails from July here:
July 15: Deriven van instanties
Hallo,
Hierbij wat uitleg over het deriven van instanties met behulp van
generische functies. Dit is geimplementeerd en beschikbaar in
de nightly builds.
Als syntax heb ik voorlopig gekozen wat makkelijk te implementeren
was. Dat kan dus nog veranderen.
derive in instantie definitie
------------------------------
Om een member van een instantie te deriven gebruik je
derive member generische_functie
in de instantie definitie. Bijvoorbeeld om == voor type T
te deriven met gEq:
instance == T where
derive == gEq
Dit genereert met behulp van generische functie gEq een
member == die een gEq voor type T uitvoert en daarbij
== gebruikt voor de elementen van constructors of velden
van records van type T.
Er kunnen natuurlijk meer members zijn die al of niet worden
gederived. Als er maar 1 member is, kan dit worden afgekort
tot:
instance == T derive gEq
Als een type parameters heeft, moet je zoals gebruikelijk
bij instanties de klassen opgeven, bijvoorbeeld:
instance == (T a) | == a derive gEq
De types van het member en de generische functie moeten
natuurlijk ongeveer hetzelfde zijn. Dat wordt nog niet
gecontroleerd, behalve dan dat de gegenereerde instantie wordt
getypeerd. De ariteit wordt wel gecontroleerd.
derive in klasse definitie
--------------------------
Om niet elke keer "derive generische_functie" in te hoeven
typen. Kun je ook het volgende doen.
In de definitie van een klasse kun je een default definieren
voor een member. Nu kun je met:
default member generische_functie
ook een generische functie opgeven als default. Bijvoorbeeld:
class equal a where
equal :: !a !a -> Bool
derive equal gEq
Als je dan een instantie declareert wordt de default gebruikt
en automatisch een instantie gegenereerd. Dus voor een type:
:: T = T Int
derive je dan een instantie met:
instance equal T
Omdat de instantie definitie in dit geval member equal
niet definieert, wordt de default uit de klasse definitie
overgenomen, die bevat een derive, waardoor de member
equal wordt gederived.
Je kunt natuurlijk ook nog:
instance equal T derive gEq
gebruiken, of met een andere generische functie.
derive van een functie in instantie definitie
---------------------------------------------
In de where van een instantie kan met een derive ook een
functie worden gegeneerd die hetzelfde doet als een member
die gederived wordt.
Bijvoorbeeld:
instance == T where
(==) a b = equal_function a b
where
derive equal_function gEq
"derive equal_function gEq" genereert een locale functie
met naam equal_function, die een gEq voor type T doet en
== gebruikt voor de elementen van constructors of velden
van records.
In dit geval roept member == deze functie aan, en
gebeurt dus hetzelfde als voor:
instance == T where
derive == gEq
Maar je kunt ook extra code toevoegen, bijvoorbeeld om
te debuggeb:
instance == T where
(==) a b
| trace_tn "== T"
= equal_function a b
where
derive equal_function gEq
of bijvoorbeeld in een functie die test waardes genereerd:
instance gen T where
gen st = [value_1,value_2 : g_gen]
where
derive g_gen gGen
Zo'n derive is alleen toegestaan in de where na een alternatief
van een member, niet in de where's van locale functies,
let's of with's.
derive van een functie in klasse definitie
------------------------------------------
Bij een definitie van een default klasse member kan ook
een locale functie worden gederived, net als bij een
instantie definitie, met dezelfde beperkingen.
Bijvoorbeeld:
class equal a where
equal :: !a !a -> Bool
equal a b
| trace_tn "derived equal"
= equal_function a b
where
derive equal_function gEq
derive met gebruik van een ander member
=======================================
Bij een "derive member_naam generische_functie_naam" wordt een
member met naam "member_naam" gegenereerd die "member_naam"
gebruikt voor de elementen van contructors of velden van
records.
Het is ook mogelijk een andere member te gebruiken. Dit kan
met "derive member_naam1 generische_functie_naam member_naam2".
Dan wordt een member met naam "member_naam1" gegenereerd die
"member_naam2" gebruikt voor de elementen van contructors of
velden van records.
Net als via het deriven van locale functies, kun je daarmee
een generische functie deriven en daarbij extra code toevoegen. In
dit geval door een extra member toe te voegen met hetzelfde type
in deze klasse, of in een andere klasse.
Bijvoorbeeld (met een andere klasse), als equal gedefineerd is:
class equal a where
equal :: !a !a -> Bool
derive equal gEq
kun je een extra klasse toevoegen:
class equal2 a where
equal2 :: !a !a -> Bool
equal2 kan dan gederived worden met:
instance equal2 T where
derive equal2 gEq equal
equal2 gebruikt nu equal, en doet daardoor hetzelfde als
een derive van equal. Nu kan equal2 gebruikt worden in
de definitie van equal:
instance equal T where
equal a b
| trace_tn "equal T"
= equal2 a b
Dit was geimplementeerd voordaar het mogelijk was om locale
functies te deriven. Misschien is dit nu niet meer nodig.
De derive met extra member kan ook gebruikt worden in een
default van een klasse definitie.
tegelijk deriven van meerdere instanties
----------------------------------------
Met generics is het mogelijk voor meerdere type tegelijk
generische functies te deriven. Bijvoorbeeld:
derive gEq T1,T2,T3
Als geen members gedefinieerd worden bij een instantie
definitie (omdat alle members gedefinieerd worden door
default (derived) class members), kan dit nu ook voor
instanties met:
instance == T1,T2,T3
opmerkingen
-----------
Members van dictionaries zijn strict. Dit kan problemen opleveren
als je i.p.v. een generische functie met ariteit 0 een gederivede
instantie gaat gebruiken. De member van een dictionary van een
generische functie met ariteit 0 is lazy voor de itask compiler.
Dit moet eigenlijk ook zo zijn voor members met ariteit 0 van
gewone klassen, maar is nog niet geimplementeerd.
Bij deriven van generische functies kunnen bij instanties geen
extra klasse constraints toegevoegd worden voor type variabelen
van het het type waarvoor gederived wordt. Bij het deriven van
instanties kan dat wel. Daardoor is zoiets als afhankelijke
generische functies vaak niet nodig.
En is het bijvoorbeeld ook mogelijk instanties te definieren of
te deriven voor unboxed lijsten. Voor generics kan dat niet.
details
-------
- alleen de generische instanties voor EITHER, PAIR, UNIT,
CONS, OBJECT, RECORD en FIELD worden gebruikt. De generische
functie moet gedefinieerd zijn.
- de generische klassen voor allerlei kinds worden niet gebruikt.
Zelfs de klasse voor kind * niet. Shorthand instanties ook niet.
- net als voor generische functies kan een bimap nodig zijn.
- instanties voor multiparameter type klassen zijn niet toegestaan.
- generische functies met afhankelijke generische functies zijn
niet geimplementeed. Dit kan waarschijnlijk wel. Maar ik kwam
tot nu toe geen voorbeeld tegen waarbij dit nodig was.
- als locale functies gederived worden, krijgen ze nu dezelfde
ariteit als het member. Dat moet eigenlijk bepaald worden
door de ariteit van de generische functie, maar kan niet
zomaar geimplementeerd worden. De syntax zo aanpassen dat
de ariteit uit de syntax kan worden bepaald is een andere
mogelijk.
Met vriendelijke groet,
John
July 16: Gederivede generische functies aanpassen
Hallo,
Bij het implementeren van het deriven van instanties met generische functies
bedacht ik een paar manieren om een instantie te deriven en toch te kunnen
aanpassen.
Een van die methodes is het deriven met een andere naam, zodat je de de instantie
zelf kunt definieren met behulp van de gederivede code, doordat je die kunt
aanroepen met die andere naam.
Dit zou je ook kunnen doen met gewone generische functies. Als je een
generische functie g hebt, definieer je dan een zelfde generische functie
met een andere naam, bijvoorbeeld g2. Als je dan een instantie van g2
zou kunnen deriven die hetzelfde doet als een instantie van g, kun je g2
voor een type T maken met:
g{|T|} a0 .. an = g2{|*|} a0 .. an
waarbij a0 .. an de argumenten voor de generische functie zijn.
De code kun je dan makkelijk aanpassen, bijvoorbeeld als je wilt debuggen:
g{|T|} a0 .. an
| trace_tn "g T"
= g2{|*|} a0 .. an
Nu bedacht ik net dat je deze g2 nu ook al kunt maken met de huidige compiler
met behulp van afhankelijke generische functies:
g2 a | g a :: type_van_g
g2 voor OBJECT en RECORD moet je dan definieren, door g voor OBJECT/RECORD
te copieren, g te veranderen in g2 en als extra tweede argument een _ toe te
voegen. Bij het exporteren van g2 voor OBJECT en RECORD gebruik je dan
with _ g, bijvoorbeeld:
derive g2 OBJECT with _ g
Daardoor weet de compiler dat je alleen g gebruikt en niet g2 en hoef je
g2 niet te definieren voor andere types, zoals PAIR en EITHER.
Ik heb dit getest voor gast en de itasks gEditor.
ggen is:
generic ggen a :: Int [Int] -> [a]
dus ggen2 wordt dan:
generic ggen2 a | ggen a :: Int [Int] -> [a]
ggen voor OBJECT is:
ggen{|OBJECT|} f n rnd = [OBJECT a \\ a<-f n rnd]
dus ggen2 is dan hetzelfde, behalve de naam en een extra _ :
ggen2{|OBJECT|} _ f n rnd = [OBJECT a \\ a<-f n rnd]
Dus aan ggen.icl voeg ik toe:
generic ggen2 a | ggen a :: Int [Int] -> [a]
ggen2{|OBJECT|} _ f n rnd = [OBJECT a \\ a<-f n rnd]
ggen2{|RECORD|} _ f n rnd = [RECORD a \\ a<-f n rnd]
en aan ggen.dcl:
generic ggen2 a | ggen a :: Int [Int] -> [a]
derive ggen2 OBJECT with _ ggen, RECORD with _ ggen
In voorbeeld testTree kun je nu:
derive ggen Color
vervangen door:
derive ggen2 Color
ggen{|Color|} n rnd = ggen2{|*|} n rnd
Maar je kunt nu ook:
ggen{|Color|} n rnd = [Blue] ++ ggen2{|*|} n rnd
of:
ggen{|Color|} n rnd = [Blue] ++ [c\\c<-ggen2{|*|} n rnd | not c=:Blue]
En voor Tree:
derive ggen2 Tree
ggen{|Tree|} f n rnd = ggen2{|*->*|} undef f n rnd
of:
ggen{|Tree|} f n rnd = [Leaf] ++ ggen2{|*->*|} undef f n rnd
of:
ggen{|Tree|} f n rnd = [Leaf] ++ [t\\t<-ggen2{|*->*|} undef f n rnd | not t=:Leaf]
gEditor is gedefinieerd in module iTasks.UI.Editor.Generic als:
generic gEditor a | gText a, JSONEncode a, JSONDecode a :: Editor a
gEditor2 zou dan worden:
generic gEditor2 a | gEditor a, gText a, JSONEncode a, JSONDecode a :: Editor a
Maar omdat de gEditor voor OBJECT a en RECORD a alleen gEditor a gebruiken, en
niet gText a, JSONEncode a en JSONDecode, kunnen we dit vereenvoudigen tot:
generic gEditor2 a | gEditor a :: Editor a
gEditor voor OBJECT en RECORD in implementatie module iTasks.UI.Editor.Generic
copieren we (dit zijn grote functies) en we passen de eerste regel aan:
gEditor{|RECORD of {grd_arity}|} {Editor|genUI=exGenUI,onEdit=exOnEdit,onRefresh=exOnRefresh,valueFromState=exValueFromState} _ _ _
wordt:
gEditor2{|RECORD of {grd_arity}|} _ {Editor|genUI=exGenUI,onEdit=exOnEdit,onRefresh=exOnRefresh,valueFromState=exValueFromState}
en:
gEditor{|OBJECT of {gtd_num_conses,gtd_conses}|} ce=:{Editor|genUI=exGenUI,onEdit=exOnEdit,onRefresh=exOnRefresh,valueFromState=exValueFromState} _ _ _
wordt:
gEditor2{|OBJECT of {gtd_num_conses,gtd_conses}|} _ ce=:{Editor|genUI=exGenUI,onEdit=exOnEdit,onRefresh=exOnRefresh,valueFromState=exValueFromState}
Dus in beide gevallen hernoemen, een _ toevoegen als 2de argument en de 3 _'s aan het einde weghalen, omdat gText,JSONEncode en JSONDecode niet nodig zijn.
In de definitie module voegen we dan toe:
generic gEditor2 a | gEditor a :: Editor a
derive gEditor2
OBJECT of {gtd_num_conses,gtd_conses} with _ gEditor,
RECORD of {grd_arity} with _ gEditor
Ik heb dit getest met BasicAPIExamples.EditorsOnCustomTypes.EnterPerson.
Om het deriven makkelijker te maken definieer ik:
class iTaskEditor2 a | gEditor2{|*|}, gText{|*|}, JSONEncode{|*|}, JSONDecode{|*|}, gEq{|*|} a
Ik kan nu:
derive class iTask Person
vervangen door:
derive class iTaskEditor2 Person
gEditor{|Person|} = gEditor2{|*|}
En gEditor{|Person|} kan ik nu aanpasen, voorbeeld om te zien wanneer hij wordt
aangeroepen:
gEditor{|Person|}
| trace_tn "gEditor Person"
= gEditor2{|*|}
Maar je zou bijvoorbeeld ook de editor kun customizen.
Met vriendelijke groet,
John