
Constrained genericity orbounded quantification means that a parametrized type or function (seeparametric polymorphism) can only be instantiated on types fulfilling some conditions, even if those conditions are not used in that function.
Say a type is called "eatable" if you can call the functioneat on it. Write a generic typeFoodBox which contains a collection of objects of a type given as parameter, but can only be instantiated on eatable types. The FoodBox shall not use the function eat in any way (i.e. without the explicit restriction, it could be instantiated on any type). The specification of a type being eatable should be as generic as possible in your language (i.e. the restrictions on the implementation of eatable types should be as minimal as possible). Also explain the restrictions, if any, on the implementation of eatable types, and show at least one example of an eatable type.
Ada allows various constraints to be specified in parameters of generics. A formal type constrained to be derived from certain base is one of them:
withAda.Containers.Indefinite_Vectors;packageNutritionistypeFoodisinterface;procedureEat(Object:inoutFood)isabstract;endNutrition;withAda.Containers;withNutrition;generictypeNew_FoodisnewNutrition.Food;packageFood_BoxesispackageFood_Vectorsis newAda.Containers.Indefinite_Vectors(Index_Type=> Positive,Element_Type=> New_Food);subtypeFood_BoxisFood_Vectors.Vector;endFood_Boxes;
The package Nutrition defines an interface of an eatable object, that is, the procedure Eat. Then a generic container package is defined with the elements to be of some type derived from Food. Example of use:
typeBananaisnewFoodwithnull record;overridingprocedureEat(Object:inoutBanana)isnull;packageBanana_Boxis newFood_Boxes(Banana);typeTomatoisnewFoodwithnull record;overridingprocedureEat(Object:inoutTomato)isnull;packageTomato_Boxis newFood_Boxes(Tomato);-- We have declared Banana and Tomato as a Food.
The Tomato_Box can only contain tomatoes; the Banana_Box can only contain bananas. You can only create boxes of eatable objects.
In C#, type constraints are made on the type hierarchy, so here we makeIEatable an interface, with anEat method. Types which are eatable would have to implement theIEatable interface and provide anEat method.
interfaceIEatable{voidEat();}
Type constraints in type parameters can be made via thewhere keyword, which allows us to qualify T. In this case, we indicate that the type argument must be a type that is a subtype ofIEatable.
usingSystem.Collections.Generic;classFoodBox<T>whereT:IEatable{List<T>food;}
For example, an eatable Apple:
classApple:IEatable{publicvoidEat(){System.Console.WriteLine("Apple has been eaten");}}
C# also has the interesting functionality of being able to require that a generic type have a default constructor. This means that the generic type can actually instantiate the objects without ever knowing the concrete type. To do so, we constrain the where clause with an additional term "new()". This must come after any other constraints. In this example, any type with a default constructor that implements IEatable is allowed.
usingSystem.Collections.GenericclassFoodMakingBox<T>whereT:IEatable,new(){List<T>food;voidMake(intnumberOfFood){this.food=newList<T>();for(inti=0;i<numberOfFood;i++){this.food.Add(newT());}}}
template<typenameT>conceptEatable=requires(Tt){t.eat();};structPotato{voideat();};structBrick{};template<EatableT>classFoodBox{};intmain(){FoodBox<Potato>lunch{};// Following leads to compile-time error//FoodBox<Brick> practical_joke{};}
Uses static assertion to disallow instantiations on incorrect types
template<typenameT>// Detection helper structstructcan_eat// Detects presence of non-const member function void eat(){private:template<typenameU,void(U::*)()>structSFINAE{};template<typenameU>staticcharTest(SFINAE<U,&U::eat>*);template<typenameU>staticintTest(...);public:staticconstexprboolvalue=sizeof(Test<T>(0))==sizeof(char);};structpotato{voideat();};structbrick{};template<typenameT>classFoodBox{//Using static assertion to prohibit non-edible typesstatic_assert(can_eat<T>::value,"Only edible items are allowed in foodbox");//Rest of class definition};intmain(){FoodBox<potato>lunch;//Following leads to compile-time error//FoodBox<brick> practical_joke;}
The technique used here is like that in theAbstract type task.
The task says that this task is only for statically typed languages, and Common Lisp is dynamically typed. However, there are many places where type declarations can be provided to the compiler, and there is user access to the type system (e.g., a user can ask whether an object is of a particular type). Via the latter mechanism, one could write a class containing a collection such that the insert method checked that the object to be inserted is of an appropriate type.
In this example, we define a classfood, and two subclasses,inedible-food andedible-food. We define a generic functioneat, and specialize it only foredible-food. We then define a predicateeatable-p which returns true only on objects for whicheat methods have been defined. Then, usingdeftype with asatisfies type specifier, we define a typeeatable to which only objects satisfyingeatable-p belong. Finally, we define a functionmake-food-box which takes, in addition to typical array creation arguments, a type specifier. The array is declared to have elements of the type that is the intersection offood and the provided type.make-eatable-food-box simply callsmake-food-box with the typeeatable.
The only shortcoming here is that the compiler isn't required to enforce the type specifications for the arrays. A custom insert function, however, could remember the specified type for the collection, and assert that inserted elements are of that type.
(defclassfood()())(defclassinedible-food(food)())(defclassedible-food(food)())(defgenericeat(foodstuff)(:documentation"Eat the foodstuff."))(defmethodeat((foodstuffedible-food))"A specialized method for eating edible-food."(formatnil"Eating ~w."foodstuff))(defuneatable-p(thing)"Returns true if there are eat methods defined for thing."(not(endp(compute-applicable-methods#'eat(listthing)))))(deftypeeatable()"Eatable objects are those satisfying eatable-p."'(satisfieseatable-p))(defunmake-food-box(extra-type&restarray-args)"Returns an array whose element-type is (and extra-type food).array-args should be suitable for MAKE-ARRAY, and any providedelement-type keyword argument is ignored."(destructuring-bind(dimensions&restarray-args)array-args(apply'make-arraydimensions:element-type`(and,extra-typefood)array-args)))(defunmake-eatable-food-box(&restarray-args)"Return an array whose elements are declared to be of type (andeatable food)."(apply'make-food-box'eatablearray-args))
Similar to Ruby version, but shows error at compile-time.
classAppledefeatendendclassCarrotdefeatendendclassFoodBox(T)definitialize(@data:Array(T)){%ifT.union?%} {% raise "All items should be eatable" unless T.union_types.all? &.has_method?(:eat) %}{%else%} {% raise "Items should be eatable" unless T.has_method?(:eat) %}{%end%}endendFoodBox.new([Apple.new,Apple.new])FoodBox.new([Apple.new,Carrot.new])FoodBox.new([Apple.new,Carrot.new,123])
Error in line 23: All items should be eatable
enumIsEdible(T)=is(typeof(T.eat));structFoodBox(T)if(IsEdible!T){T[]food;aliasfoodthis;}structCarrot{voideat(){}}staticstructCar{}voidmain(){FoodBox!CarrotcarrotsBox;// OKcarrotsBox~=Carrot();// Adds a carrot//FoodBox!Car carsBox; // Not allowed}
interfaceIEdible{voideat();}structFoodBox(T:IEdible){T[]food;aliasfoodthis;}classCarrot:IEdible{voideat(){}}classCar{}voidmain(){FoodBox!CarrotcarrotBox;// OK//FoodBox!Car carBox; // Not allowed}
It is surely arguable whether this constitutes an implementation of the above task:
/** Guard accepting only objects with an 'eat' method */def Eatable { to coerce(specimen, ejector) { if (Ref.isNear(specimen) && specimen.__respondsTo("eat", 0)) { return specimen } else { throw.eject(ejector, `inedible: $specimen`) } }}def makeFoodBox() { return [].diverge(Eatable) # A guard-constrained list}Eiffel has included support for constrained genericty since its earliest implementations (as shown in Bertrand Meyer's paper from OOPSLA '86, availableonline.)
The "eatable" characteristic is modeled by a deferred class (deferred classes are similar to abstract classes in some other languages).
deferredclassEATABLEfeature-- Basic operationseat-- Eat this eatable substancedeferredendend
ClassEATABLE can then be inherited by any other class, with the understanding that the inheriting class will have to provide an implementation for the procedureeat. Here are two such classes,APPLE andPEAR:
classAPPLEinheritEATABLEfeature-- Basic operationseat-- Consumedoprint("One apple eaten%N")endend
classPEARinheritEATABLEfeature-- Basic operationseat-- Consumedoprint("One pear eaten%N")endend
Instances of the generic classFOOD_BOX can contain any types ofEATABLE items. The constraint is shown in the formal generics part of the class declaration forFOOD_BOX:
classFOOD_BOX[G->EATABLE]inheritARRAYED_LIST[G]createmakeend
So, any declaration of typeFOOD_BOX can constrain its contents to any particular eatable type. For example:
my_apple_box:FOOD_BOX[APPLE]
The entitymy_apple_box is declared as aFOOD_BOX which can contain only apples.
Of course, constraining a particularFOOD_BOX to all types which are eatable is also allowed, and could be appropriate in certain cases, such as:
my_refrigerator:FOOD_BOX[EATABLE]
Here's a small application that uses aFOOD_BOX constrained to contain only apples:
classAPPLICATIONcreatemakefeature{NONE}-- Initializationmake-- Run application.docreatemy_apple_box.make(10)createone_applecreateone_pearmy_apple_box.extend(one_apple)-- my_apple_box.extend (one_pear)acrossmy_apple_boxasicloopic.item.eatendendfeature-- Accessmy_apple_box:FOOD_BOX[APPLE]-- My apple boxone_apple:APPLE-- An appleone_pear:PEAR-- A pearend
Notice that an instance ofPEAR is also created, and a line of code is present as a comment which would attempt to place the pear in the apple box:
-- my_apple_box.extend (one_pear)If the comment mark "--" were removed from this line of code, an compile error would occur because of the attempt to violatemy_apple_bos's constraint.
It is possible to constrain type parameters in a number of ways, including inheritance relationships and interface implementation. But for this task, the natural choice is an explicit member constraint.
type^aFoodBox// a generic type FoodBoxwhen^a:(membereat:unit->string)// with an explicit member constraint on ^a,(items:^alist)=// a one-argument constructormemberinlinex.foodItems=items// and a public read-only property// a class type that fullfills the member constrainttypeBanana()=memberx.eat()="I'm eating a banana."// an instance of a Banana FoodBoxletsomeBananas=FoodBox[Banana();Banana()]
Works with any ANS Forth
Needs the FMS-SI (single inheritance) library code located here:http://soton.mpeforth.com/flag/fms/index.html
includeFMS-SI.fincludeFMS-SILib.f:classEatable:meat."successful eat";m;class\ FoodBox is defined without inspecting for the eat message:classFoodBoxobject-listeatable-types:minit:eatable-typesinit:;m:madd:( obj -- )dupis-kindOfEatableifeatable-typesadd:elsedrop."not an eatable type"then;m:mtestbegineatable-typeseach:whileeatrepeat;m;classFoodBoxaFoodBoxEatableaEatableaEatableaFoodBoxadd:\ add the e1 object to the object-listaFoodBoxtest\ => successful eat:classbrick:meatcr."successful eat";m;classbrickabrick\ create an object that is not eatableabrickaFoodBoxadd:\ => not an eatable type:classapple<superEatable;classappleanappleanappleaFoodBoxadd:aFoodBoxtest\ => successful eat successful eat
In Fortran all checkes are done at compile time, in particular a dummy argument has to conform class.
modulecgimplicit none type,abstract::eatableend typeeatabletype,extends(eatable)::carrot_tend typecarrot_ttype::brick_t;end typebrick_ttype::foodboxclass(eatable),allocatable::foodcontains procedure,public::add_item=>add_item_fbend typefoodboxcontains subroutineadd_item_fb(this,f)class(foodbox),intent(inout)::thisclass(eatable),intent(in)::fallocate(this%food,source=f)end subroutineadd_item_fbend modulecgprogramcon_genusecgimplicit none type(carrot_t)::carrottype(brick_t)::bricktype(foodbox)::fbox! Put a carrot into the foodboxcallfbox%add_item(carrot)! Try to put a brick in - results in a compiler errorcallfbox%add_item(brick)end programcon_gen
ifort -o cg cg.f90
cg.f90(40): error #6633: The type of the actual argument differs from the type of the dummy argument. [BRICK] call fbox%add_item(brick)
gfortran -o cg cg.f90
cg.f90:41.23: call fbox%add_item(brick) 1Error: Type mismatch in argument 'f' at (1); passed TYPE(brick_t) to CLASS(eatable)
TypephysicalAsDoubleEnumfoodoyster=1troutbloaterchocolatetrufflescheesecakecreampuddingpieEndEnumTypeActualFoodnombreAsIntegersizeAsphysicalquantityAsphysicalEndTypeTypefoodboxItem(100)AsActualFoodmaxAsIntegerEndTypeSubput_(ByreffbAsfoodbox,ByvalfAsInteger,ByvalsAsphysical,ByvalqAsphysical)fb.max+=1fb.Item(fb.max).nombre=ffb.Item(fb.max).size=sfb.Item(fb.max).quantity=qEndSubSubGetNext(ByreffbAsfoodbox,ByrefStuffAsActualFood)Iffb.max>0ThenStuff=fb.Item(fb.max)fb.max-=1EndIfEndSubTypeGourmandWeightGainAsphysicalSleepTimeAsphysicalEndTypeSubeats(ByrefgAsGourmand,ByrefstuffAsActualFood)g.WeightGain+=stuff.size*stuff.quantity*0.75stuff.size=0stuff.quantity=0EndSub' TestDimAsfoodboxHamperDimAsGourmandMrGDimAsActualFoodCourseput_(Hamper,food.pudding,3,7)put_(Hamper,food.pie,7,3)GetNext(Hamper,Course)eats(MrG,Course)PrintMrG.WeightGain' result 15.75Sleep
Go's interfaces do exactly what this task wants. Eatable looks like this:
typeeatableinterface{eat()}
And the following is all it takes to define foodbox as a slice of eatables. The result is that an object of type foodbox can hold objects of any type that implements the eat method (with the function signature specified in eatable.) The definition of foodbox though, doesn't even need to enumerate the functions of eatable, much less call them. Whatever is in the interface is okay.
typefoodbox[]eatable
Here is an example of an eatable type.
typepeelfirststringfunc(fpeelfirst)eat(){// peel code goes herefmt.Println("mm, that",f,"was good!")}
The only thing it takes to make peelfirst eatable is the definition of the eat method. When the eat method is defined, peelfirst automatically becomes an eatable. We say itsatisfies the interface. Notice that "eatable" appears nowhere in the definition of peelfirst or the eat method of peelfirst.
Here is a complete program using these types.
packagemainimport"fmt"typeeatableinterface{eat()}typefoodbox[]eatabletypepeelfirststringfunc(fpeelfirst)eat(){// peel code goes herefmt.Println("mm, that",f,"was good!")}funcmain(){fb:=foodbox{peelfirst("banana"),peelfirst("mango")}f0:=fb[0]f0.eat()}
mm, that banana was good!
Atype class defines a set of operations that must be implemented by a type:
classEatableawhereeat::a->String
We just require that instances of this type class implement a functioneat which takes in the type and returns a string (I arbitrarily decided).
TheFoodBox type could be implemented as follows:
data(Eatablea)=>FoodBoxa=F[a]
The stuff before the=> specify what type classes the type variablea must belong to.
We can create an instance ofEatable at any time by providing an implementation for the functioneat. Here we define a new typeBanana, and make it an instance ofEatable.
dataBanana=Foo-- the implementation doesn't really matter in this caseinstanceEatableBananawhereeat_="I'm eating a banana"
We can declare existing types to be instances in the exact same way. The following makesDouble an eatable type:
instanceEatableDoublewhereeatd="I'm eating "++showd
Another way to make an existing type eatable is to declare all instances of another type class instances of this one. Let's assume we have another type classFood which looks like this;
classFoodawheremunch::a->String
Then we can make all instances of Food eatable usingmunch foreat with the following instance declaration:
instance(Fooda)=>Eatableawhereeatx=munchx
Neither Icon nor Unicon are statically typed.In Unicon, new types can be defined as classes. The solution shownfollows the Scala approach.
importUtils# From the UniLib package to get the Class class.classEatable:Class()endclassFish:Eatable(name)methodeat();write("Eating "+name);endendclassRock:Class(name)methodeat();write("Eating "+name);endendclassFoodBox(A)initiallyeveryitem:=!Adoif"Eatable"==item.Type()thennextelsebad:="yes"return/badendproceduremain()ifFoodBox([Fish("salmon")])thenwrite("Edible")elsewrite("Inedible")ifFoodBox([Rock("granite")])thenwrite("Edible")elsewrite("Inedible")end
Sample run:
->cgEdibleInedible->
Implementation:
coclass'Connoisseur'isEdible=:3 :00<nc<'eat__y')coclass'FoodBox'create=:3 :0collection=:0#y)add=:3 :0"0'inedible'assertisEdible_Connoisseur_ycollection=:collection,yEMPTY)
An edible type would be a class that has a verb with the name 'eat' (the task "eatable" requirement is checked on an object or class reference using the static methodisEdible_Connoisseur_).
We have also defined a 'FoodBox' container class which can only contain edible objects. (Our add method returns returns an empty result since its purpose is to add to the container, not to produce a result.)
For example:
coclass'Apple'eat=:3 :0smoutput'delicious')
And here is a quicky demo of the above:
lunch=:''conew'FoodBox'a1=:conew'Apple'a2=:conew'Apple'add__luncha1add__luncha2george=:conew'Connoisseur'add__lunchgeorge|inedible:assert
In Java type constraints are made on the type hierarchy, so here we makeEatable an interface, with aneat method. Types which are Eatable would have to implement theEatable interface and provide aneat method.
interfaceEatable{voideat();}
Type constraints in type parameters can be made via theextends keyword, indicating in this case that the type argument must be a type that is a subtype ofEatable.
importjava.util.List;classFoodBox<TextendsEatable>{publicList<T>food;}
Similarly a generic method can constrain its type parameters
public<TextendsEatable>voidfoo(Tx){}// although in this case this is no more useful than just "public void foo(Eatable x)"
ThisT does not necessarily have to be defined in the class declaration. Another method may be declared like this:
publicclassTest{public<TextendsEatable>voidbar(){}}
which has no indication of whereT is coming from. This method could be called like this:
test.<EatableClass>bar();
Thefoo method from before can figure outT from its parameter, but thisbar method needs to be told what T is.
Julia allows user defined types with inheritance. Misuse of a type generally produces a compile time error message.
abstracttypeEdibleendeat(::Edible)="Yum!"mutablestructFoodBox{T<:Edible}food::Vector{T}endstructCarrot<:Ediblevariety::AbstractStringendstructBrickvolume::Float64endc=Carrot("Baby")b=Brick(125.0)eat(c)eat(b)
MethodError: no method matching eat(::Brick)Closest candidates are: eat(!Matched::Edible) at console:2
In the following program we define an interface, Eatable, and two classes - Cheese and Meat - which implement it and must therefore implement its eat() method because the interface itself does not provide a default implementation.
We then define a generic class, FoodBox, whose type parameter, T, is constrained to an Eatable type and instantiate it using both the Cheese and Meat types:
// version 1.0.6interfaceEatable{funeat()}classCheese(valname:String):Eatable{overridefuneat(){println("Eating $name")}overridefuntoString()=name}classMeat(valname:String):Eatable{overridefuneat(){println("Eating $name")}overridefuntoString()=name}classFoodBox<T:Eatable>{privatevalfoodList=mutableListOf<T>()funadd(food:T){foodList.add(food)}overridefuntoString()=foodList.toString()}funmain(args:Array<String>){valcheddar=Cheese("cheddar")valfeta=Cheese("feta")valcheeseBox=FoodBox<Cheese>()cheeseBox.add(cheddar)cheeseBox.add(feta)println("CheeseBox contains : $cheeseBox")valbeef=Meat("beef")valham=Meat("ham")valmeatBox=FoodBox<Meat>()meatBox.add(beef)meatBox.add(ham)println("MeatBox contains : $meatBox")cheddar.eat()beef.eat()println("Full now!")}
CheeseBox contains : [cheddar, feta]MeatBox contains : [beef, ham]Eating cheddarEating beefFull now!
import morfa.type.traits;template < T >alias IsEdible = HasMember< T, "eat" >;template < T >if (IsEdible< T >)struct FoodBox{ var food: T[];}struct Carrot { func eat(): void {}}struct Car {}func main(): void { var carrotBox: FoodBox< Carrot >; // OK carrotBox.food ~= Carrot(); // Adds a carrot // var carBox: FoodBox< Car >; // Not allowed static assert( not trait(compiles, func() { var carBox: FoodBox< Car >; } ));}interface IEdible { public func eat(): void;}template < T >if (IsDerivedOf< T, IEdible >)struct FoodBox{ var food: T[];}class Carrot: IEdible { public override func eat(): void {}}class Car {}func main(): void { var carrotBox: FoodBox< Carrot >; // OK // var carBox: FoodBox< Car >; // Not allowed static assert( not trait(compiles, func() { var carBox: FoodBox< Car >; } ));}usingSystem.Collections.Generic;interfaceIEatable{Eat():void;}classFoodBox[T]:IEnumerable[T]whereT:IEatable{private_foods:list[T]=[];publicthis(){}publicthis(items:IEnumerable[T]){this._foods=$[food|foodinitems];}publicAdd(food:T):FoodBox[T]{FoodBox(food::_foods);}publicGetEnumerator():IEnumerator[T]{_foods.GetEnumerator();}}classApple:IEatable{publicthis(){}publicEat():void{System.Console.WriteLine("nom..nom..nom");}}mutableappleBox=FoodBox();repeat(3){appleBox=appleBox.Add(Apple());}foreach(appleinappleBox)apple.Eat();
nom..nom..nomnom..nom..nomnom..nom..nom
typeEatable=concepteeat(e)FoodBox[e:Eatable]=seq[e]Food=objectname:stringcount:intproceat(x:int)=echo"Eating the int: ",xproceat(x:Food)=echo"Eating ",x.count," ",x.name,"s"varints=FoodBox[int](@[1,2,3,4,5])varfs=FoodBox[Food](@[])fs.addFood(name:"Hamburger",count:3)fs.addFood(name:"Cheeseburger",count:5)forfinfs:eat(f)
All generic 'T' types associated with the FoodBox must implement the 'Eatable' interface. Generic constrains may either be an interface or a sub-classed type.
use Collection.Generic;interface Eatable { method : virtual : Eat() ~ Nil;}class FoodBox<T : Eatable> { food : List<T>;}class Plum implements Eatable { method : Eat() ~ Nil { "Yummy Plum!"->PrintLine(); }}class Genericity { function : Main(args : String[]) ~ Nil { plums : FoodBox<Plum>; }}Type constraints are made on the type hierarchy, so here we makeEatable a protocol, with aneat method. Types which are Eatable would have to implement theEatable protocol and provide aneat method.
@protocolEatable-(void)eat;@end
Type constraints in type parameters can be made via the: keyword, indicating in this case that the type argument must be a type that is a subtype ofid<Eatable>.
@interfaceFoodBox<T:id<Eatable>>:NSObject@end
OCaml handles type constraints throughmodules andmodule types.
A module type defines a set of operations that must be implemented by a module:
moduletypeEatable=sigtypetvaleat:t->unitend
We just require that module instances of this module type describe a typet and implement a functioneat which takes in the type and returns nothing.
TheFoodBox generic type could be implemented as afunctor (something which takes a module as an argument and returns another module):
moduleMakeFoodBox(A:Eatable)=structtypeelt=A.ttypet=Fofeltlistletmake_box_from_listxs=Fxsend
We can create a module that is an instance ofEatable by specifying a type providing an implementation for the functioneat. Here we define a moduleBanana, and make it an instance ofEatable.
typebanana=Foo(* a dummy type *)moduleBanana:Eatablewithtypet=banana=structtypet=bananaleteat_=print_endline"I'm eating a banana"end
We can also create modules that use an existing type as itst. The following module usesfloat as its type:
moduleEatFloat:Eatablewithtypet=float=structtypet=floatleteatf=Printf.printf"I'm eating %f\n%!"fend
Then, to make a FoodBox out of one of these modules, we need to call the functor on the module that specifies the type parameter:
moduleBananaBox=MakeFoodBox(Banana)moduleFloatBox=MakeFoodBox(EatFloat)letmy_box=BananaBox.make_box_from_list[Foo]letyour_box=FloatBox.make_box_from_list[2.3;4.5]
Unfortunately, it is kind of cumbersome in that, for every type parameter we want to use for this generic type, we will have to explicitly create a module for the resulting type (i.e.BananaBox,FloatBox). And the operations on that resulting type (i.e.make_box_from_list) are tied to each specific module.
ooRexx methods, routines, and collections are all untyped, so there are no language-level checks for type matches. Tests for identity need to be performed at runtime using mechanisms such as the object isA method.
calldinnerTime"yogurt"calldinnerTime.pizza~newcalldinnerTime.broccoli~new--amixinclassthatdefinestheinterfaceforbeing"food",and--thusexpectedtoimplementan"eat"method::classfoodmixinclassobject::methodeatabstract::classpizzasubclassfood::methodeatSay"mmmmmmmm, pizza".--mixinclassescanalsobeusedformultipleinheritance::classbroccoliinheritfood::methodeatSay"ugh, do I have to?".::routinedinnerTimeuseargdish--ooRexxargumentsaretypeless,sotestsforconstrained--typesmustbepeformedatruntime.TheisAmethodwill--checkifanobjectisoftherequiredtypeif\dish~isA(.food)thendosay"I can't eat that!"returnendelsedish~eat
I can't eat that!mmmmmmmm, pizza.ugh, do I have to?.
Generic but not too generic I trust.
macro Gluttony(vartype, capacity, foodlist)'==========================================typedef vartype physicalenum food foodlisttype ActualFood sys name physical size physical quantityend typeClass foodbox'============has ActualFood Item[capacity]sys maxmethod put(sys f, physical s,q) max++ Item[max]<=f,s,qend methodmethod GetNext(ActualFood *Stuff) if max then copy @stuff,@Item[max], sizeof Item max-- end ifend methodend classClass Gourmand'=============physical WeightGain, SleepTimemethod eats(ActualFood *stuff) WeightGain+=stuff.size*stuff.quantity*0.75 stuff.size=0 stuff.quantity=0end methodend classend macro'IMPLEMENTATION'==============Gluttony (double,100,{oyster,trout,bloater,chocolate,truffles,cheesecake,cream,pudding,pie})% small 1% medium 2% large 3% huge 7% none 0% single 1% few 3% several 7% many 12'INSTANCE'========FoodBox HamperGourmand MrG'TEST'====Hamper.put food.pudding,large,severalHamper.put food.pie,huge,fewActualFood CourseHamper.GetNext CourseMrG.eats Courseprint MrG.WeightGain 'result 15.75Gemeric type T has a constraint in the form of implementation of standard .NET interface IComparable<T> with only method CompareTo that returns integer.
functionMin<T>(a:arrayofT):T;whereT:IComparable<T>;beginResult:=a[0];forvari:=1toa.Length-1doifa[i].CompareTo(Result)<0thenResult:=a[i];end;typePoint=record(IComparable<Point>)x,y:integer;constructor(xx,yy:integer):=(x,y):=(xx,yy);functionCompareTo(p:Point):integer;beginResult:=x.CompareTo(p.x);ifResult=0thenResult:=y.CompareTo(p.y);end;end;beginvara:=Arr(newPoint(2,3),newPoint(1,4),newPoint(3,1));Print(Min(a));end.
(1,4)
No interfaces per se, but you can explicitly test manually for these sort of things.Needs 0.8.1+
includebuiltins\structs.easstructsclassfoodboxsequencecontents={}procedureadd(classfood)-- (aside: class food is 100% abstract here...-- ie: class is *the* root|anything class,-- and food is just an arbitrary name)integert=structs:get_field_flags(food,"eat")ift!=SF_PROC+SF_PUBLICthenthrow("not edible")-- no public method eat...endif-- you might also want something like this:-- t = structs:fetch_field(food,"eat")-- if t=NULL then-- throw("eat not implemented")-- end ifthis.contents=append(this.contents,food)endprocedureproceduredine()integerl=length(this.contents)strings=iff(l=1?"":"s")printf(1,"foodbox contains %d item%s\n",{l,s})fori=1toldoclassfood=this.contents[i];--food.eat(); -- error...-- If you don't define an [abstract] eat() method, or use-- "class", you end up having to do something like this:integereat=structs:fetch_field(food,"eat")eat(food)endforendprocedureendclassfoodboxlunchbox=new()classfruitstringnameprocedureeat()printf(1,"mmm... %s\n",{this.name})endprocedureendclassfruitbanana=new({"banana"})classclaystringname="fletton"endclassclaybrick=new()lunchbox.add(banana)trylunchbox.add(brick)-- throws exceptioncatcheprintf(1,"%s line %d error: %s\n",{e[E_FILE],e[E_LINE],e[E_USER]})endtrylunchbox.dine()
test.exw line 9 error: not ediblefoodbox contains 1 itemmmm... banana
With added milkshake, note thatour foodboxcan containboth fruitand drink!
- we would not even need to change the foodbox to be allowed to put sandwiches and biscuits in it either!
Of course you could trivially create a fruit-only foodbox just by changing the one line(ie 8) to add(fruit food).
What is much harder(/left as an exercise) is to dynamically change those kinds of restrictions at run-time.
abstractclassediblestringnameprocedureeat();-- (virtual)endclassclassfoodbox2sequencecontents={}procedureadd(ediblefood)iffood.eat=NULLthen-- (optional)throw("eat() not implemented")endifthis.contents=append(this.contents,food)endprocedureproceduredine()integerl=length(this.contents)strings=iff(l=1?"":"s")printf(1,"foodbox2 contains %d item%s\n",{l,s})fori=1toldo-- this.contents[i].eat() -- not supported, sorry!-- compiler needs some more type hints, such as:ediblefood=this.contents[i]food.eat()endforendprocedureendclassfoodbox2lunchbox2=new()classfruit2extendsedibleprocedureeat()printf(1,"mmm... %s\n",{this.name})endprocedureendclassfruit2banana2=new({"banana"})classclay2stringname="common fletton"endclassclay2brick2=new()classdrinkextendsedibleprocedureeat()printf(1,"slurp... %s\n",{this.name})endprocedureendclassdrinkmilkshake=new({"milkshake"})lunchbox2.add(banana2)trylunchbox2.add(brick2)-- triggers typecheckcatcheprintf(1,"%s line %d: %s\n",{e[E_FILE],e[E_LINE],e[E_USER]})endtrylunchbox2.add(milkshake)lunchbox2.dine()
test.exw line 8: type check failure, food is {"struct","clay2",8,1}foodbox2 contains 2 itemsmmm... bananaslurp... milkshake(class +Eatable)(dm eat> () (prinl "I'm eatable") )(class +FoodBox)# obj(dm set> (Obj) (unless (method 'eat> Obj) # Check if the object is eatable (quit "Object is not eatable" Obj) ) (=: obj Obj) ) # If so, set the object(let (Box (new '(+FoodBox)) Eat (new '(+Eatable)) NoEat (new '(+Bla))) (set> Box Eat) # Works (set> Box NoEat) ) # Gives an error
$384320489 -- Object is not eatable? (show Box) $384320487 (+FoodBox) obj $384320488? (show Box 'obj)$384320488 (+Eatable)? (show NoEat) $384320489 (+Bla)
Pluto is dynamically typed and so any constraint on class instantiation can only be checked at run time.
-- Abstract class.classeatablefunctioneat()end-- override in child classendclassfoodboxextendseatableprivatecontentsfunction__construct(contents)if!contents:checkall(|c|->cinstanceofeatable)thenerror("All foodbox elements must be eatable.")endself.contents=contentsendfunctiongetContents()returnself.contentsendend-- Inherits from eatable and overrides eat() method.classpieextendseatablefunction__construct(privatefilling)endfunctioneat()print($"{self.filling} pie, yum!")endend-- Not an eatableclassbicycleendlocalitems={newpie("Apple"),newpie("Gooseberry")}localfb=newfoodbox(items)fb:getContents():foreach(|item|->item:eat())print()items:insert(newbicycle())fb=newfoodbox(items)-- throws an error because bicycle not eatable
Apple pie, yum!Gooseberry pie, yum!pluto: Constrained_genericity.pluto:11: All foodbox elements must be eatable.stack traceback:[C]: in function 'error'Constrained_genericity.pluto:11: in field '__construct'[Pluto-injected code]: in local 'Pluto_operator_new'Constrained_genericity.pluto:36: in main chunk[C]: in ?
This example uses a runtime checkable Protocol to defineEdible. AddingShoe to aFoodBox will cause an error with static type checkers, and we raise aTypeError at runtime if a non-edible object is added to aFoodBox.
"""Constrained genericity. Requires Python >= 3.9."""fromtypingimportGenericfromtypingimportProtocolfromtypingimportTypeVarfromtypingimportruntime_checkableT=TypeVar("T",covariant=True)@runtime_checkableclassEdible(Protocol[T]):defeat(self)->T:...classFoodBox(Generic[T]):def__init__(self,*food:Edible[T]):# Runtime type checkingforiteminfood:ifnotisinstance(item,Edible):raiseTypeError(f"expected food, found{item.__class__.__name__}")self.contents=foodclassCheese:defeat(self)->None:print("eating cheese")classShoe:defwear(self)->None:print("wearing shoe")if__name__=="__main__":box=FoodBox[None](Cheese())forfoodinbox.contents:food.eat()# This fails static type checking.box=FoodBox[None](Cheese(),Shoe())
eating cheeseTypeError: expected food, found Shoe
edible<%> objects simply need to state that they implement the interface in the second argument toclass*. By doing so they will be forced to implementeat.
#langracket(module+test(requiretests/eli-tester));; This is all that an object should need to properly implement.(defineedible<%>(interface()[eat(->mvoid?)]))(define(generic-container<%>containee/c)(interface()[contents(->m(listofcontainee/c))][insert(->mcontainee/cvoid?)][remove-at(->mexact-nonnegative-integer?containee/c)][count(->mexact-nonnegative-integer?)]))(define((generic-box-mixincontainee/c)%)(->i([containee/ccontract?])(rv(containee/c)(implementation?/c(generic-container<%>containee/c))))(class*%((generic-container<%>containee/c))(super-new)(definelempty)(define/public(contents)l)(define/public(inserto)(set!l(consol)))(define/public(remove-ati)(begin0(list-refli)(append(takeli)(dropl(add1i)))))(define/public(count)(lengthl))));; As I understand it, a "Food Box" from the task is still a generic... i.e.;; you will specify it down ;; to an "apple-box%" so: food-box%-generic is still;; generic. food-box% will take any kind of food.(define/contract(food-box-mixinT%)(->(or/c(λ(i)(eq?edible<%>i))(implementation?/cedible<%>))(make-mixin-contract))(generic-box-mixin(and/c(is-a?/cedible<%>)(is-a?/cT%))))(module+test(defineinteger-box%((generic-box-mixininteger?)object%))(defineinteger-box(newinteger-box%))(defineapple%(class*object%(edible<%>)(super-new)(define/public(eat)(displayln"nom!"))))(definebanana%(class*object%(edible<%>)(super-new)(define/public(eat)(displayln"peel.. peel... nom... nom!"))))(definesemolina%(class*object%(); <-- empty interfaces clause(super-new);; you can call eat on it... but it's not explicitly (or even vaguely);; edible<%>(define/public(eat)(displayln"blech!"))));; this will take any object that is edible<%> and edible<%> (therefore all;; edible<%> objects)(defineany-food-box(new((food-box-mixinedible<%>)object%)));; this will take any object that is edible and an apple<%>;; (therefore only apple<%>s)(defineapple-food-box(new((food-box-mixinapple%)object%)))(test;; Test generic boxes(sendinteger-boxinsert22)(sendinteger-boxinsert"a string")=error>exn:fail:contract?;; Test the food box that takes any edible<%>(sendany-food-boxinsert(newapple%))(sendany-food-boxinsert(newbanana%))(sendany-food-boxinsert(newsemolina%))=error>exn:fail:contract?;; Test the food box that takes any apple%(sendapple-food-boxinsert(newapple%))(sendapple-food-boxinsert(newbanana%))=error>exn:fail:contract?(sendapple-food-boxinsert(newsemolina%))=error>exn:fail:contract?(sendapple-food-boxcount)=>1;; Show that you cannot make a food-box from the non-edible<%> semolina cannot(implementation?semolina%edible<%>)=>#f(new((food-box-mixinsemolina%)object%))=error>exn:fail:contract?))
All the tests pass. Look at the tests to see what generates an exception (i.e.not allowed at runtime) and what does not.
(formerly Perl 6)
subsetEatableofAnywhere { .^can('eat') };classCake {methodeat() {...} }roleFoodBox[Eatable] {has%.foodbox;}classYummydoesFoodBox[Cake] { }# composes correctly# class Yucky does FoodBox[Int] { } # fails to composemyYummy$foodbox .=new;say$foodbox;
Yummy.new(foodbox => {})classFoodboxdefinitialize(*food)raiseArgumentError,"food must be eadible"unlessfood.all?{|f|f.respond_to?(:eat)}@box=foodendendclassFruitdefeat;endendclassApple<Fruit;endpFoodbox.new(Fruit.new,Apple.new)# => #<Foodbox:0x00000001420c88 @box=[#<Fruit:0x00000001420cd8>, #<Apple:0x00000001420cb0>]>pFoodbox.new(Apple.new,"string can't eat")# => test1.rb:3:in `initialize': food must be eadible (ArgumentError)
// This declares the "Eatable" constraint. It could contain no function.traitEatable{fneat();}// This declares the generic "FoodBox" type,// whose parameter must satisfy the "Eatable" constraint.// The objects of this type contain a vector of eatable objects.structFoodBox<T:Eatable>{_data:Vec<T>,}// This implements the functions associated with the "FoodBox" type.// This statement is not required, but here it is used// to declare a handy "new" constructor.impl<T:Eatable>FoodBox<T>{fnnew()->FoodBox<T>{FoodBox::<T>{_data:Vec::<T>::new()}}}// This declares a simple type.structBanana{}// This makes the "Banana" type satisfy the "Eatable" constraint.// For that, every declaration inside the declaration of "Eatable"// must be implemented here.implEatableforBanana{fneat(){}}// This makes also the primitive "char" type satisfy the "Eatable" constraint.implEatableforchar{fneat(){}}fnmain(){// This instantiate a "FoodBox" parameterized by the "Banana" type.// It is allowed as "Banana" implements "Eatable".let_fb1=FoodBox::<Banana>::new();// This instantiate a "FoodBox" parameterized by the "char" type.// It is allowed, as "char" implements "Eatable".let_fb2=FoodBox::<char>::new();// This instantiate a "FoodBox" parameterized by the "bool" type.// It is NOT allowed, as "bool" does not implement "Eatable".//let _fb3 = FoodBox::<bool>::new();}
abstract class $EDIBLE is eat;end;class FOOD < $EDIBLE is readonly attr name:STR; eat is #OUT + "eating " + self.name + "\n"; end; create(name:STR):SAME is res ::= new; res.name := name; return res; end;end;class CAR is readonly attr name:STR; create(name:STR):SAME is res ::= new; res.name := name; return res; end;end;class FOODBOX{T < $EDIBLE} is private attr list:LLIST{T}; create:SAME is res ::= new; res.list := #; return res; end; add(c :T) is self.list.insert_back(c); end; elt!:T is loop yield self.list.elt!; end; end;end;class MAIN is main is box ::= #FOODBOX{FOOD}; -- ok box.add(#FOOD("Banana")); box.add(#FOOD("Amanita Muscaria")); box2 ::= #FOODBOX{CAR}; -- not ok box2.add(#CAR("Punto")); -- but compiler let it pass! -- eat everything loop box.elt!.eat; end; end;end;The GNU Sather compiler v1.2.3 let the "box2" pass, even though it shouldn't. Read e.g.this tutorial's section
Scala can constrain types in many different ways. This specific constrain, for the type to contain a particular method, can be written as this:
typeEatable={defeat:Unit}classFoodBox(coll:List[Eatable])caseclassFish(name:String){defeat{println("Eating "+name)}}valfoodBox=newFoodBox(List(newFish("salmon")))
A more extensive discussion on genericity in Scala and some of the constrains that can be imposed can be found onParametric Polymorphism.
classFoodBox(*food{.all{.respond_to(:eat)}}){}classFruit{methodeat{...}}classApple<Fruit{}sayFoodBox(Fruit(),Apple()).dump#=> FoodBox(food: [Fruit(), Apple()])sayFoodBox(Apple(),"foo")#!> ERROR: class `FoodBox` !~ (Apple, String)
Here we makeEatable a protocol, with aneat method. Types which are Eatable would have to conform to theEatable protocol and provide aneat method.
protocolEatable{funceat()}
Type constraints in type parameters can be made via the: syntax, indicating in this case that the type argument must be a type that is a subtype ofEatable.
structFoodBox<T:Eatable>{varfood:[T]}
Similarly a generic function or method can constrain its type parameters
funcfoo<T:Eatable>(x:T){}// although in this case this is no more useful than just "func foo(x: Eatable)"
defstructWe implement a food-box-defining macro, which checks at macro expansion time that the contained type isedible. The macro generates a structure of the specified name, which has aset-food method that additionally performs a run-time check against the exact variant of theedible type that was given to the macro.
(defmacro define-food-box (name food-type : supers . clauses) (unless (subtypep food-type 'edible) (error "~s requires edible type, not ~s" %fun% food-type)) ^(defstruct ,name ,supers food (:method set-food (me food) (unless (typep food ',food-type) (error "~s: requires ~s object, not ~s" %fun% ',food-type food)) (set me.food food)) ,*clauses))
Instead of the type-basedsubtypep check, we could easily check for the existence of methods; e.g. test for the presence of a static slot using(static-slot-p food-type 'eat), or more specifically that it's a function:(functionp (static-slot food-type 'eat)). These tests will blow up if the macro'sfood-type argument isn't a struct type.
In the interactive session below, we:
define-food-box cannot be used with a type argument that isn't derived fromedibleedible and then one derived from it calledperishable.define-food-box to define a box calledfridge which holdsperishable. This works becauseperishable isediblefridge and show that itsset-food method doesn't take an integer, or anedible; only aperishable.$ txr -i generic.tl This area is under 24 hour TTY surveillance.1> (define-food-box fridge string)** define-food-box requires edible type, not string** during evaluation of form (error "~s requires edible type, not ~s" 'define-food-box food-type)** ... an expansion of (error "~s requires edible type, not ~s" %fun% food-type)** which is located at generic.tl:31> (defstruct edible ())#<struct-type edible>2> (defstruct perishable (edible))#<struct-type perishable>3> (define-food-box fridge perishable)#<struct-type fridge>4> (new fridge)#S(fridge food nil)5> *4.(set-food 42)** (set-food fridge): requires perishable object, not 42** during evaluation of form (error "~s: requires ~s object, not ~s" '(set-food fridge) 'perishable food)** ... an expansion of (error "~s: requires ~s object, not ~s" %fun% 'perishable food)** which is located at expr-3:15> *4.(set-food (new edible))** (set-food fridge): requires perishable object, not #S(edible)** during evaluation of form (error "~s: requires ~s object, not ~s" '(set-food fridge) 'perishable food)** ... an expansion of (error "~s: requires ~s object, not ~s" %fun% 'perishable food)** which is located at expr-3:15> *4.(set-food (new perishable))#S(perishable)6> *4#S(fridge food #S(perishable))
defstruct clauseWrappingdefstruct is a heavy-handed approach that may be difficult to retrofit into an existing code base. One possible issue is that two developers write such a macro, and then someone needs to use both of them for the same class. But each macro wants to write its own entiredefstruct form.
Here, we instead use a custom clause to inject thefood slot,set-food method, and the static and dynamic checks. The mechanisms remain identical.
(define-struct-clause :food-box (food-type :form form) (unless (subtypep food-type 'edible) (compile-error form "~s requires edible type, not ~s" :food-box food-type)) ^(food (:method set-food (me food) (unless (typep food ',food-type) (error "~s: requires ~s object, not ~s" %fun% ',food-type food)) (set me.food food))))
$ txr -i generic.tl Apply today for a TXR credit card, and get 1MB off your next allocation.1> (defstruct fridge () (:food-box string))** expr-1:1: defstruct: :food-box requires edible type, not string1> (defstruct edible ())#<struct-type edible>2> (defstruct perishable (edible))#<struct-type perishable>3> (defstruct fridge () (:food-box perishable))#<struct-type fridge>4> (new fridge)#S(fridge food nil)5> *4.(set-food 42)** (set-food fridge): requires perishable object, not 42** during evaluation of form (error "~s: requires ~s object, not ~s" '(set-food fridge) 'perishable food)** ... an expansion of (error "~s: requires ~s object, not ~s" %fun% 'perishable food)** which is located at expr-3:15> *4.(set-food (new edible))** (set-food fridge): requires perishable object, not #S(edible)** during evaluation of form (error "~s: requires ~s object, not ~s" '(set-food fridge) 'perishable food)** ... an expansion of (error "~s: requires ~s object, not ~s" %fun% 'perishable food)** which is located at expr-3:15> *4.(set-food (new perishable))#S(perishable)
Wren is dynamically typed and so any constraint on class instantiation can only be checked at run time.
// abstract classclassEatable{eat(){/* override in child class */}}classFoodBox{constructnew(contents){if(contents.any{|e|!(eisEatable)}){Fiber.abort("All FoodBox elements must be eatable.")}_contents=contents}contents{_contents}}// Inherits from Eatable and overrides eat() method.classPieisEatable{constructnew(filling){_filling=filling}eat(){System.print("%(_filling) pie, yum!")}}// Not an Eatable.classBicycle{constructnew(){}}varitems=[Pie.new("Apple"),Pie.new("Gooseberry")]varfb=FoodBox.new(items)fb.contents.each{|item|item.eat()}System.print()items.add(Bicycle.new())fb=FoodBox.new(items)// throws an error because Bicycle not eatable
Apple pie, yum!Gooseberry pie, yum!All FoodBox elements must be eatable.[./constrained_genericity line 9] in init new(_)[./constrained_genericity line 12] in [./constrained_genericity line 34] in (script)
zkl isn't statically typed so the test is done at runtime.
This is a slightly different take on the task, keeping the editables and rejecting the garbage.
class Eatable{ var v; fcn eat{ println("munching ",self.topdog.name); }}class FoodBox{ fcn init(food1,food2,etc){ editable,garbage:=vm.arglist.filter22("isChildOf",Eatable); var contents=editable; if(garbage) println("Rejecting: ",garbage); }}class Apple(Eatable){} class Nuts(Eatable){} class Foo{}FoodBox(Apple,"boogers",Nuts,Foo).contents.apply2("eat");Rejecting: L("boogers",Class(Foo))munching Applemunching Nuts