Taggad fackförening

Inom datavetenskap är en taggad union , även kallad variant , variantpost , valtyp , diskriminerad union , disjunkt union , summatyp eller samprodukt , en datastruktur som används för att hålla ett värde som kan anta flera olika, men fasta, typer . Endast en av typerna kan användas åt gången, och ett taggfält anger uttryckligen vilken som används. Det kan ses som en typ som har flera "cases", som vart och ett ska hanteras korrekt när den typen manipuleras. Detta är avgörande för att definiera rekursiva datatyper, där någon komponent av ett värde kan ha samma typ som själva värdet, till exempel för att definiera en typ för att representera träd, där det är nödvändigt att särskilja multinodsunderträd och löv. Liksom vanliga fackföreningar kan taggade fackföreningar spara lagring genom att överlappa lagringsutrymmen för varje typ, eftersom endast en används åt gången.

Beskrivning

Taggade fackföreningar är viktigast i funktionella språk som ML och Haskell , där de kallas datatyper (se algebraisk datatyp ) och kompilatorn kan verifiera att alla fall av ett taggat förbund alltid hanteras, vilket undviker många typer av fel. De kan dock konstrueras på nästan vilket språk som helst och är mycket säkrare än otaggade fackföreningar, ofta helt enkelt kallade fackföreningar, som liknar varandra men inte uttryckligen håller reda på vilken medlem av facket som för närvarande används.

Taggade fackföreningar åtföljs ofta av konceptet med en typkonstruktor , som är liknande men inte samma som en konstruktor för en klass. Typkonstruktörer producerar en taggad unionstyp, givet den ursprungliga taggtypen och motsvarande typ.

Matematiskt motsvarar taggade fackföreningar osammanhängande eller diskriminerade fackföreningar , vanligtvis skrivna med +. Givet ett element av en disjunkt förening A + B , är det möjligt att avgöra om det kom från A eller B. Om ett element ligger i båda kommer det att finnas två effektivt distinkta kopior av värdet i A + B , en från A och en från B .

I typteorin kallas en taggad union en summatyp . Summatyper är de dubbla produkttyperna . Notationerna varierar, men vanligtvis kommer summatypen A + B med två introduktionsformer ( injektioner ) inj 1 : A A + B och inj 2 : B A + B . Elimineringsformuläret är fallanalys, känd som mönstermatchning i programmeringsspråk av ML-stil : om e har typ A + B och e 1 och e 2 har typ under antagandena x : A och y : B resp har typen . Summatypen motsvarar intuitionistisk logisk disjunktion under Curry-Howard-korrespondensen .

En uppräknad typ kan ses som ett degenererat fall: en märkt förening av enhetstyper . Den motsvarar en uppsättning nullära konstruktörer och kan implementeras som en enkel taggvariabel, eftersom den inte innehåller några ytterligare data förutom taggens värde.

Många programmeringstekniker och datastrukturer, inklusive rep , lat utvärdering , klasshierarki (se nedan), aritmetik med godtycklig precision , CDR-kodning , inriktningsbiten och andra typer av taggade pekare , etc. implementeras vanligtvis med någon sorts taggad union.

Ett taggat förbund kan ses som den enklaste typen av självbeskrivande dataformat . Taggen för den taggade föreningen kan ses som den enklaste typen av metadata .

Fördelar och nackdelar

Den främsta fördelen med en taggad union framför en otaggad union är att alla åtkomster är säkra, och kompilatorn kan till och med kontrollera att alla ärenden hanteras. Otaggade fackföreningar är beroende av programlogik för att korrekt identifiera det för närvarande aktiva fältet, vilket kan resultera i konstigt beteende och svåra att hitta buggar om den logiken misslyckas.

Den primära fördelen med en taggad union jämfört med en enkel post som innehåller ett fält för varje typ är att den sparar lagring genom att överlappa lagring för alla typer. Vissa implementeringar reserverar tillräckligt med lagringsutrymme för den största typen, medan andra dynamiskt justerar storleken på ett taggat unionsvärde efter behov. När värdet är oföränderligt är det enkelt att allokera precis så mycket lagringsutrymme som behövs.

Den största nackdelen med taggade fackföreningar är att taggen tar plats. Eftersom det vanligtvis finns ett litet antal alternativ, kan taggen ofta klämmas in i 2 eller 3 bitar varhelst utrymme kan hittas, men ibland är inte ens dessa bitar tillgängliga. I det här fallet kan ett användbart alternativ vara vikta , beräknade eller kodade taggar , där taggvärdet beräknas dynamiskt från innehållet i unionsfältet. Vanliga exempel på detta är användningen av reserverade värden , där till exempel en funktion som returnerar ett positivt tal kan returnera -1 för att indikera fel, och sentinel-värden , som oftast används i taggade pekare .

Ibland används otaggade fackföreningar för att utföra konverteringar på bitnivå mellan typer, så kallade omtolkningar i C++. Taggade fackföreningar är inte avsedda för detta ändamål; vanligtvis tilldelas ett nytt värde när taggen ändras.

Många språk stödjer i viss utsträckning en universell datatyp , vilket är en typ som inkluderar alla värden av alla andra typer, och ofta tillhandahålls ett sätt att testa den faktiska typen av ett värde av den universella typen. Dessa kallas ibland för varianter . Medan universella datatyper är jämförbara med taggade fackföreningar i sin formella definition, inkluderar typiska taggade fackföreningar ett relativt litet antal fall, och dessa fall bildar olika sätt att uttrycka ett enda sammanhängande koncept, såsom en datastrukturnod eller instruktion. Det finns också en förväntning om att alla möjliga fall av ett taggat fackförbund kommer att behandlas när det används. Värdena för en universell datatyp är inte relaterade och det finns inget genomförbart sätt att hantera dem alla.

Liksom alternativtyper och undantagshantering används ibland taggade fackföreningar för att hantera förekomsten av exceptionella resultat. Ofta viks dessa taggar till typen som "reserverade värden", och deras förekomst kontrolleras inte konsekvent: detta är en ganska vanlig källa till programmeringsfel. Denna användning av taggade fackföreningar kan formaliseras som en monad med följande funktioner:

där "värde" och "fel" är konstruktörerna av unionstypen, A och B är giltiga resultattyper och E är typen av feltillstånd. Alternativt kan samma monad beskrivas med retur och två ytterligare funktioner, fmap och join :

Exempel

Säg att vi ville bygga ett binärt träd av heltal. I ML skulle vi göra detta genom att skapa en datatyp så här:

   
                      datatypträd  =  Blad  |  _  Nod  av  (  int  *  träd  *  träd  ) 

Detta är en taggad union med två fall: ett, bladet, används för att avsluta en bana i trädet och fungerar ungefär som ett nollvärde skulle göra i imperativa språk. Den andra grenen har en nod, som innehåller ett heltal och ett vänster och höger underträd. Leaf och Node är konstruktörerna som gör det möjligt för oss att faktiskt producera ett visst träd, till exempel:

         Nod  (  5  ,  Nod  (  1  ,  Leaf  ,  Leaf  ),  Nod  (  3  ,  Leaf  ,  Node  (  4  ,  Leaf  ,  Leaf  ))) 

som motsvarar detta träd:

The tree produced by the above constructors

Nu kan vi enkelt skriva en typsäker funktion som, säg, räknar antalet noder i trädet:

   0
      
           fun  countNoder  (  Leaf  )  =  |  countNodes  (  Node  (  int  ,  vänster  ,  höger  ))  =  1  +  countNodes  (  vänster  )  +  countNodes  (  höger  ) 

Tidslinje för språkstöd

1960-talet

I ALGOL 68 kallas taggade fackföreningar för united modes , taggen är implicit och case -konstruktionen används för att bestämma vilket fält som är taggat:

mode node = union ( real , int , compl , sträng );

Användningsexempel för unionsfall av nod :

 nod  n := "1234";  case  n  in  (  real  r): print(("real:", r)), (  int  i): print(("int:", i)), (  compl  c): print(("compl:", c)), (  sträng  s): print(("sträng:", s))  out  print(("?:", n))  esac 

1970- och 1980-talen

Även om i första hand endast funktionella språk som ML (från 1970-talet) och Haskell (från 1990-talet) ger en central roll till taggade förbund och har befogenhet att kontrollera att alla ärenden hanteras, har andra språk stöd även för taggade förbund. Men i praktiken kan de vara mindre effektiva i icke-funktionella språk på grund av optimeringar som aktiveras av funktionella språkkompilatorer som kan eliminera explicita taggkontroller och undvika explicit lagring av taggar . [ citat behövs ]

Pascal , Ada och Modula-2 kallar dem variantposter (formellt diskriminerad typ i Ada) och kräver att taggfältet skapas manuellt och taggvärdena specificeras, som i detta Pascal-exempel:

     
       
                  
                  
                    
                       
                        
                       
	       typ  shapeKind  =  (  kvadrat  ,  rektangel  ,  cirkel  )  ;  shape  =  post  centerx  :  heltal  ;  centery  :  heltal  ;  case  type  :  shapeKind  of  square  :  (  sida  :  heltal  )  ;  rektangel  :  (  bredd  ,  höjd  :  heltal  )  ;  cirkel  :  (  radie  :  heltal  )  ;  slut  ; 

och denna Ada-motsvarighet:

     
     
     
     
     
        
           
        
            
        
           
    




 typ  Shape_Kind  är  (  Square  ,  Rectangle  ,  Circle  );  typ  Shape  (  Kind  :  Shape_Kind  )  är  posten  Center_X  :  Heltal  ;  Center_Y  :  Heltal  ;  case  Typ  är  när  kvadrat  =>  sida  :  heltal  ;  när  rektangel  =>  bredd  ,  höjd  :  heltal  ;  när  Cirkel  =>  Radie  :  Heltal  ;  slutfall  ;  _  slutpost  ;  -- Varje försök att komma åt en medlem vars existens beror  -- på ett visst värde hos diskriminanten, medan  -- diskriminanten inte är den förväntade, ger upphov till ett fel. 

I C och C++ kan en taggad fackförening skapas från otaggade fackföreningar med en strikt åtkomstdisciplin där taggen alltid kontrolleras:

      

  
     
     
      
     
                       
              
                     
    


    
      
     


      
      
      


 enum  ShapeKind  {  Square  ,  Rectangle  ,  Circle  };  struct  Shape  {  int  centerx  ;  int  centery  ;  enum  ShapeKind  typ  ;  union  {  struct  {  int  sida  ;  };  /* Square */  struct  {  int  width  ,  height  ;  };  /* Rektangel */  struct  {  int  radius  ;  };  /* Cirkel */  };  };  int  getSquareSide  (  struct  Shape  *  s  )  {  assert  (  s  ->  kind  ==  Square  );  returnera  s  ->  sida  ;  }  void  setSquareSide  (  struct  Shape  *  s  ,  int  sida  )  {  s  ->  kind  =  Square  ;  s  ->  sida  =  sida  ;  }  /* och så vidare */ 

Så länge förbundsfälten endast nås via funktionerna kommer åtkomsterna att vara säkra och korrekta. Samma tillvägagångssätt kan användas för kodade taggar; vi avkodar helt enkelt taggen och kontrollerar den sedan vid varje åtkomst. Om ineffektiviteten hos dessa taggkontroller är ett problem kan de tas bort automatiskt i den slutliga versionen.

C och C++ har också språkstöd för ett särskilt taggat förbund: possibly-null- pekaren . Detta kan jämföras med alternativtypen i ML eller Maybe -typen i Haskell, och kan ses som en taggad pekare : en taggad union (med en kodad tagg) av två typer:

  • Giltiga tips,
  • En nollpekartyp med bara ett värde, null , som indikerar ett exceptionellt tillstånd.

Tyvärr verifierar inte C-kompilatorer att noll-fallet alltid hanteras, och detta är en särskilt utbredd källa till fel i C-kod, eftersom det finns en tendens att ignorera exceptionella fall.

2000-talet

En avancerad dialekt av C som kallas Cyclone har omfattande inbyggt stöd för taggade fackföreningar.

Enumtyperna i språken Rust , Haxe och Swift fungerar också som taggade fackföreningar.

Variantbiblioteket från Boost har visat att det var möjligt att implementera en säker taggad union som ett bibliotek i C++, som kan besökas med funktionsobjekt.

   

      
    
              
    

       
    
              
    


    
 

    
  struct  display  :  boost  ::  static_visitor  <  void  >  {  void  operator  ()(  int  i  )  {  std  ::  cout  <<  "Det är en int, med värde "  <<  i  <<  std  ::  endl  ;  }  void  operator  ()(  const  std  ::  string  &  s  )  {  std  ::  cout  <<  "Det är en sträng, med värde "  <<  s  <<  std  ::  endl  ;  }  };  boost  ::  variant  <  int  ,  std  ::  sträng  >  v  =  42  ;  boost  ::  applicera_besökare  (  visa  (),  v  );  boost  ::  variant  <  int  ,  std  ::  string  >  v  =  "hej värld"  ;  boost  ::  applicera_besökare  (  visa  (),  v  ); 

Scala har fallklasser:

   
    
         

            förseglad  abstrakt  klass  Trädfallsobjekt  Löv  sträcker  sig  Trädfallsklass  Nod  (  värde  :  Int  ,  vänster  :  Träd  ,  höger  :  Träd  )  sträcker  sig  Trädval  träd  =  Nod  (  5  ,  Nod  (  1  ,  Löv  ,  Löv  )  ,  Nod  (  3  ,  Löv  ,  Nod  )  _  (  4  ,  Leaf  ,  Leaf  ))) 

Eftersom klasshierarkin är förseglad kan kompilatorn kontrollera att alla ärenden hanteras i en mönstermatchning:

  
         
              
 tree  match  {  case  Node  (  x  ,  _  ,  _  )  =>  println  (  "top level node value: "  +  x  )  case  Leaf  =>  println  (  "top level node is a leaf"  )  } 

Scalas fallklasser tillåter också återanvändning genom subtyping:

      
          
            
           förseglad  abstrakt  klass  Form  (  centerX  :  Int  ,  centerY  :  Int  )  fallklass  Square  (  sida  :  Int  ,  centerX  :  Int  ,  centerY  :  Int  )  sträcker  sig  Form  (  centerX  ,  centerY  )  fallklass  Rektangel  (  längd  :  Int  ,  höjd  :  Int  ,  centerX  :  _  Int  ,  centerY  :  Int  )  utökar  Form  (  centerX  ,  centerY  )  fallklass  Cirkel  (  radie  :  Int  ,  centerX  :  Int  ,  centerY  :  Int  )  utökar  Form  (  centerX  ,  centerY  )  _ 

F# har diskriminerat fackföreningar:

  
   
            

          typ  Träd  =  |  Löv  |  Värdenod  :  int  *  vänster  :  Träd  *  höger  :  Träd  låt  träd  =  Nod  (  5  ,  Nod  (  1  ,  Leaf  ,  Leaf  )  ,  Nod  (  3  ,  Leaf  ,  Nod  (  4  ,  Leaf  ,  Leaf  ))  )   

Eftersom de definierade fallen är uttömmande kan kompilatorn kontrollera att alla fall hanteras i en mönstermatchning:

  
        
               matcha  träd  med  |  Nod  (  x  ,  _,  _)  ->  printfn  "toppnivå nodvärde: %i"  x  |  Leaf  ->  printfn  "toppnivånod är ett löv" 

Haxes enums fungerar också som taggade fackföreningar:

  
  
  
  
    
 enum  Färg  {  Röd  ;  Grönt  ;  Blå  ;  Rgb  (  r  :  Int  ,  g  :  Int  ,  b  :  Int  );  } 

Dessa kan matchas med ett switchuttryck:

  
    
    
    
       
 switch  (  color  )  {  case  Red  :  trace  (  "Färgen var röd"  );  case  Green  :  trace  (  "Färgen var grön"  );  case  Blue  :  trace  (  "Färgen var blå"  );  case  Rgb  (  r  ,  g  ,  b  ):  trace  (  "Färgen hade ett rött värde på"  +  r  );  } 

Nim har objektvarianter som liknar deklarationen i Pascal och Ada:


    
      
    
      
      
     
       
     
        
     
        typ  ShapeKind  =  enum  skSquare  ,  skRektangel  ,  skCirkel  Form  =  objekt  centerX  ,  centerY  :  int  case  type  :  ShapeKind  av  skSquare  :  sida  :  int  av  skRektangel  :  längd  ,  höjd  :  int  av  skCircle  :  radie  :  int 

Makron kan användas för att emulera mönstermatchning eller för att skapa syntaktisk socker för att deklarera objektvarianter, ses här som implementerat av paketet patty :

 

    
  
    

 
  
      

   
     0 
   0   

    

    
   
     0
        


      import  patty  proc  `~`  [  A  ]  (  a  :  A  ) :  ref  A  =  nytt  (  resultat  )  resultat  [ ]  =  en  variant  Lista  [  A  ]  :  Noll  Cons  (  x  :  A  ,  xs  :  ref  List  [  A  ]  )  proc  listHjälp  [  A  ]  (  xs  :  seq  [  A  ]  ):  ​​Lista  [  A  ]  =  om  xs  .  len  ==  :  Noll  [  A  ]  ()  annars  :  Nackdelar  (  xs  [  ]  ,  ~  listHelper  (  xs  [  1  ..  xs  .  high  ]  ))  proc  lista  [  A  ]  (  xs  :  varargs  [  A  ]  ):  Lista  [  A  ]  =  listHelper  (  @  xs  )  proc  summa  (  xs  :  List  [  int  ]  ):  int  =  (  block  :  match  xs  :  Noll  :  Nackdelar  (  y  ,  ys  )  :  y  +  summa  (  ys  [ ]  )  )  ekosumma  (  lista  (  1  ,  2  ,  3  ,  4  ,  5  )) 

2010-talet

Enums läggs till i Scala 3, vilket gör att vi kan skriva om de tidigare Scala-exemplen mer kortfattat:

 
   
        

    
           
             
            enum  Träd  [  +  T  ]  :  kasus  Lövfall  Nod  (  x  :  Int  ,  vänster  :  Träd  [  T  ],  höger  :  Träd  [  T  ])  enum  Form  (  centerX  :  Int  ,  centerY  :  Int  ):  case  Square  (  sida  :  Int  ,  centerX  :  Int  ,  centerY  :  Int  )  förlänger  Form  (  centerY  ,  centerX  )  fall  Rektangel  (  längd  :  Int  ,  höjd  :  Int  ,  centerX  :  Int  ,  centerY  :  Int  )  förlänger  Form  (  centerX  ,  centerY  )  fall  Cirkel  (  radie  :  Int  ,  centerX  :  Int )  ,  centerY  :  Int  )  utökar  formen  (  centerX  ,  centerY  ) 

Språket Rust har omfattande stöd för taggade fackföreningar, kallade enums. Till exempel:

  
    
      
 enum  Träd  {  Blad  ,  Nod  (  i64  ,  Box  <  Träd  >  ,  Box  <  Träd  >  )  } 

Det tillåter också matchning på fackföreningar:

   
    
    0  
     
          


   
      
                
          0
    


  låt  träd  =  Träd  ::  Nod  (  2  ,  Box  ::  ny  (  Träd  ::  Nod  (  ,  Box  ::  ny  (  Träd  ::  Leaf  ),  Box  ::  ny  (  Träd  ::  Leaf  ))),  Box  ::  ny  (  Träd  ::  Nod  (  3  ,  Box  ::  ny  (  Träd  ::  Leaf  ),  Box  ::  ny  (  Träd  ::  Nod  (  4  ,  Box  ::  ny  (  Träd  ::  Leaf  ),  Box  ::  ny  (  Träd  ::  Leaf  )))))  );  fn  add_values  ​​(  tree  :  Tree  )  ->  i64  {  match  tree  {  Tree  ::  Node  (  v  ,  a  ,  b  )  =>  v  +  add_values  ​​(  *  a  )  +  add_values  ​​(  *  b  ),  Tree  ::  Leaf  =>  }  }  assert_eq !  (  add_values  ​​(  träd  ),  9  ); 

Rusts felhanteringsmodell förlitar sig mycket på dessa taggade fackföreningar, särskilt Option<T> -typen, som är antingen None eller Some(T) , och Result<T, E> -typen, som är antingen Ok(T) eller Err(E) ) .

Swift har också ett stort stöd för taggade fackföreningar via uppräkningar. Till exempel:

  
     
        


   
    
    0  
        


      
      
        
             

     
         0
    


   enum  Träd  {  case  leaf  indirect  case  nod  (  Int  ,  Tree  ,  Tree  )  }  låt  träd  =  Träd  .  nod  (  2  ,  .  nod  (  ,  .  blad  ,  .  blad  ),  .  nod  (  3  ,  .  blad  ,  .  nod  (  4  ,  .  blad  ,  .  blad  ))  )  func  add_values  ​​(  _  träd  :  Träd  )  ->  Int  {  byta  träd  {  fall  låt  .  nod  (  v  ,  a  ,  b  ):  return  v  +  add_values  ​​(  a  )  +  add_values  ​​(  b  )  case  .  leaf  :  return  }  }  assert  (  add_values  ​​(  träd  )  ==  9  ) 

Med TypeScript är det möjligt att skapa taggade förbund också. Till exempel:

     

        

     

   
   
  
   
     
    
        
        
  
   
     
    
        
     
       
      
          
          
    
  


  
      
         
            
         
            
            
            
             
     
 gränssnitt  Leaf  {  kind  :  "leaf"  ;  }  gränssnitt  Node  {  kind  :  "nod"  ;  värde  :  antal  ;  vänster  :  Träd  ;  höger  :  Träd  ;  }  typ  Träd  =  Löv  |  Nodkonst  rot  :  Träd  =  {  typ  :  "nod"  ,  värde  :  5  ,  vänster  :  {  typ  :  "  nod"  ,  värde  :  1  ,  vänster  :  {  typ  :  "löv"  },  höger  :  {  typ  :  "löv"  }  }  ,  höger  :  {  typ  :  "nod"  ,  värde  :  3  ,  vänster  :  {  typ  :  "blad"  },  höger  :  {  typ  :  "nod"  ,  värde  :  4  ,  vänster  :  {  typ  :  "löv"  },  höger  :  {  typ  :  "blad"  }  }  }  }  funktionsbesök  (  träd  :  träd  )  {  switch  (  träd  .  typ  )  {  case  "leaf"  :  break  case  "  nod"  :  console  .  log  (  träd  .  värde  )  besök  (  träd  .  vänster  )  besök  (  träd  .  höger  )  bryta  }  } 

Python 3.9 introducerar stöd för att skriva annoteringar som kan användas för att definiera en taggad fackföreningstyp (PEP-593):

  
         
    
 Valuta  =  Annoterad  [  TypedDict  (  'Currency'  ,  {  'dollars'  :  float  ,  'pounds'  :  float  },  total  =  False  ),  TaggedUnion  ,  ] 

C++17 introducerar std::variant och constexpr if

      

 

   

 

     
     


 

   
    
  
       
    
          
    
        
    
         
         

         
         
    
    
    
      
       
    
  


 använder  Tree  =  std  ::  variant  <  struct  Leaf  ,  struct  Node  >  ;  struct  Leaf  {  std  ::  strängvärde  ;  _  };  struct  Node  {  Träd  *  vänster  =  nullptr  ;  Träd  *  höger  =  nullptr  ;  };  struct  Transverser  {  mall  <  typnamn  T  >  void  operator  ()(  T  &&  v  )  {  if  constexpr  (  std  ::  is_same_v  <  T  ,  Leaf  &>  )  {  std  ::  cout  <<  v  .  värde  <<  "  \n  "  ;  }  else  if  constexpr  (  std  ::  is_same_v  <  T  ,  Node  &>  )  {  if  (  v  .  left  !=  nullptr  )  std  ::  visit  (  Transverser  {},  *  v  .  left  );  if  (  v  .  höger  !=  nullptr  )  std  ::  besök  (  Transverser  {},  *  v  .  right  );  }  else  {  // Uttrycket !sizeof(T) är alltid false  static_assert  (  !  sizeof  (  T  ),  "icke uttömmande besökare!"  );  };  }  };  /*Trädskog = ...;  std::visit(Transverser{}, skog);*/ 

Klasshierarkier som taggade fackföreningar

I en typisk klasshierarki inom objektorienterad programmering kan varje underklass kapsla in data som är unika för den klassen. Metadata som används för att utföra virtuell metodsökning (till exempel objektets vtable -pekare i de flesta C++-implementeringar) identifierar underklassen och fungerar så effektivt som en tagg som identifierar den specifika data som lagras av instansen (se RTTI ). Ett objekts konstruktor ställer in denna tagg, och den förblir konstant under objektets livstid.

Ändå involverar en klasshierarki sann subtyppolymorfism ; den kan utökas genom att skapa ytterligare underklasser av samma bastyp, som inte kunde hanteras korrekt under en tagg/utskick-modell. Därför är det vanligtvis inte möjligt att göra ärendeanalys eller sändning på ett subobjekts "tagg" som man skulle göra för taggade fackföreningar. Vissa språk som Scala tillåter att basklasser "förseglas" och förenar taggade fackföreningar med förseglade basklasser.

Se även

externa länkar