Hello.
I am trying to discover design patterns and how they are implemented in Common Lisp.
According to Peter Norvig (http://norvig.com/design-patterns/design-patterns.pdf) and this source (https://wiki.c2.com/?AreDesignPatternsMissingLanguageFeatures) some design pattern are more simple in Common Lisp, or are actually not needed at all because the language can be extended to achieve the same thing: extendability, separation, abstraction.
Taking an Abstract Factory for example, the sources say that metaclasses can help do this. I’ve read a bit about metaclasses and the MOP but I fail to see how. I could probably come up with something.
A GUI framework for example. I could create something like this :
— (defclass meta-button (standard-class) ((gui-button-backend :initform nil :accessor gui-button-backend)))
(defmethod closer-mop:validate-superclass ((class meta-button) (superclass standard-class)) t)
(defclass button () () (:metaclass meta-button)) (defun make-button () (make-instance 'button)) —
And configure a QT or GTK, Motif (or whatever) button backend to the metaclass on startup (or so). But I think this would just be the pattern somehow turned into a metaclass. Doesn’t feel like the right thing.
Does anyone know what Peter Norvig had in mind when he said that metaclasses can be used instead of Abstract Factory pattern?
Cheers, Manfred
On Sat, 6 Feb 2021 at 13:46, Manfred Bergmann manfred.bergmann@me.com wrote:
Does anyone know what Peter Norvig had in mind when he said that metaclasses can be used instead of Abstract Factory pattern?
Are you talking about slides 12 and 13? Peter does not mention metaclasses there or anywhere else in his slides. I believe his point is that make-instance is the factory. Classes are first-class values and make-instance accepts a class as its first argument. If you use qt-backend, gtk-backend, motif-backend classes, at some point you can make the decision about which class to instantiate and pass it to make-instance. He categorizes this pattern as invisible, which might explain why you're having trouble seeing it. :-)
Cheers, Luís
Thank you for sending this. I had just started, so look at the time you saved me!
—Scott
On Feb 6, 2021, at 10:17 AM, Luís Oliveira luismbo@gmail.com wrote:
On Sat, 6 Feb 2021 at 13:46, Manfred Bergmann manfred.bergmann@me.com wrote:
Does anyone know what Peter Norvig had in mind when he said that metaclasses can be used instead of Abstract Factory pattern?
Are you talking about slides 12 and 13? Peter does not mention metaclasses there or anywhere else in his slides. I believe his point is that make-instance is the factory. Classes are first-class values and make-instance accepts a class as its first argument. If you use qt-backend, gtk-backend, motif-backend classes, at some point you can make the decision about which class to instantiate and pass it to make-instance. He categorizes this pattern as invisible, which might explain why you're having trouble seeing it. :-)
Cheers, Luís
Hi.
Am 06.02.2021 um 16:14 schrieb Luís Oliveira luismbo@gmail.com:
On Sat, 6 Feb 2021 at 13:46, Manfred Bergmann manfred.bergmann@me.com wrote:
Does anyone know what Peter Norvig had in mind when he said that metaclasses can be used instead of Abstract Factory pattern?
Are you talking about slides 12 and 13? Peter does not mention metaclasses there or anywhere else in his slides. I believe his point is that make-instance is the factory. Classes are first-class values and make-instance accepts a class as its first argument. If you use qt-backend, gtk-backend, motif-backend classes, at some point you can make the decision about which class to instantiate and pass it to make-instance. He categorizes this pattern as invisible, which might explain why you're having trouble seeing it. :-)
OK. I kind of get it. To simple to be visible. :)
But this is kind of also possible in i.e. Java where you can say: Foo.class.newInstance() which uses reflection and is not normally used. But is effectively similar to make-instance `foo. And yet Abstract Factory pattern in Java exists and is usually done using parallel hierarchies, as he describes.
Manfred
On Sat, 6 Feb 2021 at 16:29, Manfred Bergmann manfred.bergmann@me.com wrote:
But this is kind of also possible in i.e. Java where you can say: Foo.class.newInstance() which uses reflection and is not normally used. But is effectively similar to make-instance `foo.
My Java is getting rustier and rustier, but maybe you can tell me why the following program doesn't compile:
class Foo { } class Bar extends Foo { } class Baz extends Foo { }
class Main { public static void main(String[] args) { makeAndPrint(Foo.class); makeAndPrint(Bar.class); makeAndPrint(Baz.class); }
public static void makeAndPrint(Class<Foo> klass) { try { Object o = klass.newInstance(klass); System.out.println("Created: " + o.toString()); } catch (Exception e) { System.err.println("Failed: " + klass); } } }
The rough equivalent in Common Lisp would be:
(defclass foo () ()) (defclass bar (foo) ()) (defclass baz (foo) ())
(mapcar #'make-instance '(foo bar baz))
Luís
Class::newInstance() doesn't have any parameters (also, it's deprecated: better to use getConstructor() or getDeclaredConstructor() and call newInstance() on it).
Also, the generic agreement on your calls to makeAndPrint is failing.
This does what you wanted:
jshell> class Foo { } | modified class Foo
jshell> class Bar extends Foo { } | modified class Bar
jshell> class Baz extends Foo { } | modified class Baz
jshell> class Main { ...> public static void main(String[] args) { ...> makeAndPrint(Foo.class); ...> makeAndPrint(Bar.class); ...> makeAndPrint(Baz.class); ...> } ...> ...> public static void makeAndPrint(Class<? extends Foo> klass) { ...> try { ...> Object o = klass.newInstance(); ...> System.out.println("Created: " + o.toString()); ...> } catch (Exception e) { ...> System.err.println("Failed: " + klass); ...> } ...> } ...> } | created class Main
jshell> Main.main(new String[]{}) Created: REPL.$JShell$11$Foo@6a4f787b Created: REPL.$JShell$12$Bar@7d9d1a19 Created: REPL.$JShell$13$Baz@39c0f4a
On Sat, 6 Feb 2021 at 20:56, Luís Oliveira luismbo@gmail.com wrote:
On Sat, 6 Feb 2021 at 16:29, Manfred Bergmann manfred.bergmann@me.com wrote:
But this is kind of also possible in i.e. Java where you can say: Foo.class.newInstance() which uses reflection and is not normally used. But is effectively similar to make-instance `foo.
My Java is getting rustier and rustier, but maybe you can tell me why the following program doesn't compile:
class Foo { } class Bar extends Foo { } class Baz extends Foo { }
class Main { public static void main(String[] args) { makeAndPrint(Foo.class); makeAndPrint(Bar.class); makeAndPrint(Baz.class); }
public static void makeAndPrint(Class<Foo> klass) { try { Object o = klass.newInstance(klass); System.out.println("Created: " + o.toString()); } catch (Exception e) { System.err.println("Failed: " + klass); } } }
The rough equivalent in Common Lisp would be:
(defclass foo () ()) (defclass bar (foo) ()) (defclass baz (foo) ())
(mapcar #'make-instance '(foo bar baz))
Luís
Also, a slightly Lispier way to do what you want:
List.of(Foo.class, Bar.class, Baz.class).stream().map(c -> { try { return c.newInstance(); } catch(Exception e) { e.printStackTrace(); return null; } }).collect(Collectors.toList());
The Stream API and lambdas make it almost pleasant to work with Java, but exception handling is still a bit tricky...
On Sat, 6 Feb 2021 at 21:06, Rudi Araújo rudi.araujo@gmail.com wrote:
Class::newInstance() doesn't have any parameters (also, it's deprecated: better to use getConstructor() or getDeclaredConstructor() and call newInstance() on it).
Also, the generic agreement on your calls to makeAndPrint is failing.
This does what you wanted:
jshell> class Foo { } | modified class Foo
jshell> class Bar extends Foo { } | modified class Bar
jshell> class Baz extends Foo { } | modified class Baz
jshell> class Main { ...> public static void main(String[] args) { ...> makeAndPrint(Foo.class); ...> makeAndPrint(Bar.class); ...> makeAndPrint(Baz.class); ...> } ...> ...> public static void makeAndPrint(Class<? extends Foo> klass) { ...> try { ...> Object o = klass.newInstance(); ...> System.out.println("Created: " + o.toString()); ...> } catch (Exception e) { ...> System.err.println("Failed: " + klass); ...> } ...> } ...> } | created class Main
jshell> Main.main(new String[]{}) Created: REPL.$JShell$11$Foo@6a4f787b Created: REPL.$JShell$12$Bar@7d9d1a19 Created: REPL.$JShell$13$Baz@39c0f4a
On Sat, 6 Feb 2021 at 20:56, Luís Oliveira luismbo@gmail.com wrote:
On Sat, 6 Feb 2021 at 16:29, Manfred Bergmann manfred.bergmann@me.com wrote:
But this is kind of also possible in i.e. Java where you can say: Foo.class.newInstance() which uses reflection and is not normally used. But is effectively similar to make-instance `foo.
My Java is getting rustier and rustier, but maybe you can tell me why the following program doesn't compile:
class Foo { } class Bar extends Foo { } class Baz extends Foo { }
class Main { public static void main(String[] args) { makeAndPrint(Foo.class); makeAndPrint(Bar.class); makeAndPrint(Baz.class); }
public static void makeAndPrint(Class<Foo> klass) { try { Object o = klass.newInstance(klass); System.out.println("Created: " + o.toString()); } catch (Exception e) { System.err.println("Failed: " + klass); } } }
The rough equivalent in Common Lisp would be:
(defclass foo () ()) (defclass bar (foo) ()) (defclass baz (foo) ())
(mapcar #'make-instance '(foo bar baz))
Luís
-- He who binds to himself a joy Doth the winged life destroy But he who kisses the joy as it flies Lives in Eternitys sun rise
- William Blake
On Sat, 6 Feb 2021 at 20:07, Rudi Araújo rudi.araujo@gmail.com wrote:
Class::newInstance() doesn't have any parameters (also, it's deprecated: better to use getConstructor() or getDeclaredConstructor() and call newInstance() on it).
I guess this bit about getConstructor() explains why it'd be more convenient to use a Factory, or the Factory method pattern, or some dependency injection framework.
Also, the generic agreement on your calls to makeAndPrint is failing.
Ah, <? extends Foo> was the key bit I was missing, thanks. :-)
On Sat, 6 Feb 2021 at 20:25, Rudi Araújo rudi.araujo@gmail.com wrote:
The Stream API and lambdas make it almost pleasant to work with Java, but exception handling is still a bit tricky...
Yes, yes, almost. 🙄
Luís
Am 06.02.2021 um 21:44 schrieb Luís Oliveira luismbo@gmail.com:
On Sat, 6 Feb 2021 at 20:07, Rudi Araújo rudi.araujo@gmail.com wrote:
Class::newInstance() doesn't have any parameters (also, it's deprecated: better to use getConstructor() or getDeclaredConstructor() and call newInstance() on it).
I guess this bit about getConstructor() explains why it'd be more convenient to use a Factory, or the Factory method pattern, or some dependency injection framework.
Yeah. Could be. But this constructor thingy could be hidden in a function similar as you would create a constructor function make-foo in Common Lisp. The reflection stuff is not considered a good practice in certain types of applications.
Dependency injection is about something else IMO. Well, Abstract Factory is about it, too, inversion of control. It allows you to create something without having to know the concrete type and without having to have a source dependency on it. In Common Lisp this could be solved easily by just separating a protocol from the implementation, maybe in separate packages.
Manfred
Nobody in the list community ever invented a fancy pants term like “dependency injection” because it’s so obvious how to do this that nobody thought to give it a name.
—Scott
On Feb 6, 2021, at 4:07 PM, Manfred Bergmann manfred.bergmann@me.com wrote:
Am 06.02.2021 um 21:44 schrieb Luís Oliveira luismbo@gmail.com:
On Sat, 6 Feb 2021 at 20:07, Rudi Araújo rudi.araujo@gmail.com wrote: Class::newInstance() doesn't have any parameters (also, it's deprecated: better to use getConstructor() or getDeclaredConstructor() and call newInstance() on it).
I guess this bit about getConstructor() explains why it'd be more convenient to use a Factory, or the Factory method pattern, or some dependency injection framework.
Yeah. Could be. But this constructor thingy could be hidden in a function similar as you would create a constructor function make-foo in Common Lisp. The reflection stuff is not considered a good practice in certain types of applications.
Dependency injection is about something else IMO. Well, Abstract Factory is about it, too, inversion of control. It allows you to create something without having to know the concrete type and without having to have a source dependency on it. In Common Lisp this could be solved easily by just separating a protocol from the implementation, maybe in separate packages.
Manfred
I know I could look it up in Wikipedia, but posing the question here may probably generate more amusement.
WTF is a “dependency injection”?
MA
On Sat, 6 Feb 2021 at 22:18, Scott McKay swmckay@gmail.com wrote:
Nobody in the list community ever invented a fancy pants term like “dependency injection” because it’s so obvious how to do this that nobody thought to give it a name.
—Scott
On Feb 6, 2021, at 4:07 PM, Manfred Bergmann manfred.bergmann@me.com
wrote:
Am 06.02.2021 um 21:44 schrieb Luís Oliveira luismbo@gmail.com:
On Sat, 6 Feb 2021 at 20:07, Rudi Araújo rudi.araujo@gmail.com
wrote:
Class::newInstance() doesn't have any parameters (also, it's
deprecated: better to use getConstructor() or getDeclaredConstructor() and call newInstance() on it).
I guess this bit about getConstructor() explains why it'd be more convenient to use a Factory, or the Factory method pattern, or some dependency injection framework.
Yeah. Could be. But this constructor thingy could be hidden in a function similar as you
would create a constructor function make-foo in Common Lisp.
The reflection stuff is not considered a good practice in certain types
of applications.
Dependency injection is about something else IMO. Well, Abstract Factory
is about it, too, inversion of control.
It allows you to create something without having to know the concrete
type and without having to have a source dependency on it.
In Common Lisp this could be solved easily by just separating a protocol
from the implementation, maybe in separate packages.
Manfred
--
Marco Antoniotti, Associate Professor tel. +39 - 02 64 48 79 01 DISCo, Università Milano Bicocca U14 2043 http://bimib.disco.unimib.it Viale Sarca 336 I-20126 Milan (MI) ITALY
You have a class. This class uses some other class. But by using or creating an instance of this other class directly you create a dependency on something concrete. That’s not what you want, because you might want to replace this with something else if required. For example with a mock or fake implementation in a test. ‚Dependency injection‘ allows you to declare this dependency with just an interface/protocol and have some other facility (the dependency injection framework) ‚inject‘ a concrete object at run-time. A similar thing could certainly be done by just using a constructor parameter (strategy pattern). But I think the important part here is the dependency on just an interface and not on a concrete implementation. For flexibility.
Manfred
Am 06.02.2021 um 22:27 schrieb Marco Antoniotti marco.antoniotti@unimib.it:
I know I could look it up in Wikipedia, but posing the question here may probably generate more amusement.
WTF is a “dependency injection”?
MA
On Sat, 6 Feb 2021 at 22:18, Scott McKay swmckay@gmail.com wrote:
Nobody in the list community ever invented a fancy pants term like “dependency injection” because it’s so obvious how to do this that nobody thought to give it a name.
—Scott
On Feb 6, 2021, at 4:07 PM, Manfred Bergmann manfred.bergmann@me.com wrote:
Am 06.02.2021 um 21:44 schrieb Luís Oliveira luismbo@gmail.com:
On Sat, 6 Feb 2021 at 20:07, Rudi Araújo rudi.araujo@gmail.com wrote: Class::newInstance() doesn't have any parameters (also, it's deprecated: better to use getConstructor() or getDeclaredConstructor() and call newInstance() on it).
I guess this bit about getConstructor() explains why it'd be more convenient to use a Factory, or the Factory method pattern, or some dependency injection framework.
Yeah. Could be. But this constructor thingy could be hidden in a function similar as you would create a constructor function make-foo in Common Lisp. The reflection stuff is not considered a good practice in certain types of applications.
Dependency injection is about something else IMO. Well, Abstract Factory is about it, too, inversion of control. It allows you to create something without having to know the concrete type and without having to have a source dependency on it. In Common Lisp this could be solved easily by just separating a protocol from the implementation, maybe in separate packages.
Manfred
-- Marco Antoniotti, Associate Professor tel. +39 - 02 64 48 79 01 DISCo, Università Milano Bicocca U14 2043 http://bimib.disco.unimib.it Viale Sarca 336 I-20126 Milan (MI) ITALY
We’ve never needed them in Common Lisp because: * functions have always been first class * classes have always been first class
In a clunky language like Java, you go through all these gyrations whereas in Lisp you just pass a function or a class as a normal argument to avoid wiring in dependencies.
—Scott
On Feb 6, 2021, at 4:39 PM, Manfred Bergmann manfred.bergmann@me.com wrote:
You have a class. This class uses some other class. But by using or creating an instance of this other class directly you create a dependency on something concrete. That’s not what you want, because you might want to replace this with something else if required. For example with a mock or fake implementation in a test. ‚Dependency injection‘ allows you to declare this dependency with just an interface/protocol and have some other facility (the dependency injection framework) ‚inject‘ a concrete object at run-time. A similar thing could certainly be done by just using a constructor parameter (strategy pattern). But I think the important part here is the dependency on just an interface and not on a concrete implementation. For flexibility.
Manfred
Am 06.02.2021 um 22:27 schrieb Marco Antoniotti marco.antoniotti@unimib.it:
I know I could look it up in Wikipedia, but posing the question here may probably generate more amusement.
WTF is a “dependency injection”?
MA
On Sat, 6 Feb 2021 at 22:18, Scott McKay swmckay@gmail.com wrote:
Nobody in the list community ever invented a fancy pants term like “dependency injection” because it’s so obvious how to do this that nobody thought to give it a name.
—Scott
On Feb 6, 2021, at 4:07 PM, Manfred Bergmann manfred.bergmann@me.com wrote:
Am 06.02.2021 um 21:44 schrieb Luís Oliveira luismbo@gmail.com:
On Sat, 6 Feb 2021 at 20:07, Rudi Araújo rudi.araujo@gmail.com wrote: Class::newInstance() doesn't have any parameters (also, it's deprecated: better to use getConstructor() or getDeclaredConstructor() and call newInstance() on it).
I guess this bit about getConstructor() explains why it'd be more convenient to use a Factory, or the Factory method pattern, or some dependency injection framework.
Yeah. Could be. But this constructor thingy could be hidden in a function similar as you would create a constructor function make-foo in Common Lisp. The reflection stuff is not considered a good practice in certain types of applications.
Dependency injection is about something else IMO. Well, Abstract Factory is about it, too, inversion of control. It allows you to create something without having to know the concrete type and without having to have a source dependency on it. In Common Lisp this could be solved easily by just separating a protocol from the implementation, maybe in separate packages.
Manfred
-- Marco Antoniotti, Associate Professor tel. +39 - 02 64 48 79 01 DISCo, Università Milano Bicocca U14 2043 http://bimib.disco.unimib.it Viale Sarca 336 I-20126 Milan (MI) ITALY
Functions and Classes are first-class in Java et.al. also for a while. It’s also possible to pass around a class (not the instance of the class). And yet, due to the static type nature, any class receiving a class as a parameter creates a source dependency and the compiler enforces that the class is known (compiled). Because a class is a concrete thing. That’s why in statically typed languages one uses interfaces and that’s where Abstract Factories, or dependency injection comes in. Since Lisp is dynamic and classes are basically represented by just symbols the handing of this is a bit easier and less clunky. But fundamentally, you also can’t create an instance of a class in Common Lisp from just the symbol. The class definition must be known.
Manfred
Am 06.02.2021 um 22:50 schrieb Scott McKay swmckay@gmail.com:
We’ve never needed them in Common Lisp because:
- functions have always been first class
- classes have always been first class
In a clunky language like Java, you go through all these gyrations whereas in Lisp you just pass a function or a class as a normal argument to avoid wiring in dependencies.
—Scott
On Feb 6, 2021, at 4:39 PM, Manfred Bergmann manfred.bergmann@me.com wrote:
You have a class. This class uses some other class. But by using or creating an instance of this other class directly you create a dependency on something concrete. That’s not what you want, because you might want to replace this with something else if required. For example with a mock or fake implementation in a test. ‚Dependency injection‘ allows you to declare this dependency with just an interface/protocol and have some other facility (the dependency injection framework) ‚inject‘ a concrete object at run-time. A similar thing could certainly be done by just using a constructor parameter (strategy pattern). But I think the important part here is the dependency on just an interface and not on a concrete implementation. For flexibility.
Manfred
Am 06.02.2021 um 22:27 schrieb Marco Antoniotti marco.antoniotti@unimib.it:
I know I could look it up in Wikipedia, but posing the question here may probably generate more amusement.
WTF is a “dependency injection”?
MA
On Sat, 6 Feb 2021 at 22:18, Scott McKay swmckay@gmail.com wrote:
Nobody in the list community ever invented a fancy pants term like “dependency injection” because it’s so obvious how to do this that nobody thought to give it a name.
—Scott
On Feb 6, 2021, at 4:07 PM, Manfred Bergmann manfred.bergmann@me.com wrote:
Am 06.02.2021 um 21:44 schrieb Luís Oliveira luismbo@gmail.com:
On Sat, 6 Feb 2021 at 20:07, Rudi Araújo rudi.araujo@gmail.com wrote: Class::newInstance() doesn't have any parameters (also, it's deprecated: better to use getConstructor() or getDeclaredConstructor() and call newInstance() on it).
I guess this bit about getConstructor() explains why it'd be more convenient to use a Factory, or the Factory method pattern, or some dependency injection framework.
Yeah. Could be. But this constructor thingy could be hidden in a function similar as you would create a constructor function make-foo in Common Lisp. The reflection stuff is not considered a good practice in certain types of applications.
Dependency injection is about something else IMO. Well, Abstract Factory is about it, too, inversion of control. It allows you to create something without having to know the concrete type and without having to have a source dependency on it. In Common Lisp this could be solved easily by just separating a protocol from the implementation, maybe in separate packages.
Manfred
-- Marco Antoniotti, Associate Professor tel. +39 - 02 64 48 79 01 DISCo, Università Milano Bicocca U14 2043 http://bimib.disco.unimib.it Viale Sarca 336 I-20126 Milan (MI) ITALY
Am So., 7. Feb. 2021 um 09:20 Uhr schrieb Manfred Bergmann < manfred.bergmann@me.com>:
But fundamentally, you also can’t create an instance of a class in Common Lisp from just the symbol. The class definition must be known.
This simply is untrue. You can use FIND-CLASS to find a class named by a symbol and then instantiate it.
-Hans
Am 07.02.2021 um 09:23 schrieb Hans Hübner hans.huebner@gmail.com:
Am So., 7. Feb. 2021 um 09:20 Uhr schrieb Manfred Bergmann manfred.bergmann@me.com: But fundamentally, you also can’t create an instance of a class in Common Lisp from just the symbol. The class definition must be known.
This simply is untrue. You can use FIND-CLASS to find a class named by a symbol and then instantiate it.
Well, OK. Sure. But when I do that I have again a dependency on the concrete class, or? And it would be similar as knowing the class right from the start.
Am So., 7. Feb. 2021 um 09:35 Uhr schrieb Manfred Bergmann < manfred.bergmann@me.com>:
Am 07.02.2021 um 09:23 schrieb Hans Hübner hans.huebner@gmail.com:
Am So., 7. Feb. 2021 um 09:20 Uhr schrieb Manfred Bergmann <
manfred.bergmann@me.com>:
But fundamentally, you also can’t create an instance of a class in
Common Lisp from just the symbol. The class definition must be known.
This simply is untrue. You can use FIND-CLASS to find a class named by
a symbol and then instantiate it.
Well, OK. Sure. But when I do that I have again a dependency on the concrete class, or? And it would be similar as knowing the class right from the start.
You don't. You have a dependency on the name of a class. The name could refer to two entirely different classes between invocations of FIND-CLASS. The name could also come from an external source. Thus, this is purely a run-time dependency and it would be quite possible that FIND-CLASS returns NIL if given a symbol that does not designate a currently-defined class.
... and I can always CHANGE-CLASS, can't I?
On Sun, Feb 7, 2021 at 9:46 AM Hans Hübner hans.huebner@gmail.com wrote:
Am So., 7. Feb. 2021 um 09:35 Uhr schrieb Manfred Bergmann < manfred.bergmann@me.com>:
Am 07.02.2021 um 09:23 schrieb Hans Hübner hans.huebner@gmail.com:
Am So., 7. Feb. 2021 um 09:20 Uhr schrieb Manfred Bergmann <
manfred.bergmann@me.com>:
But fundamentally, you also can’t create an instance of a class in
Common Lisp from just the symbol. The class definition must be known.
This simply is untrue. You can use FIND-CLASS to find a class named by
a symbol and then instantiate it.
Well, OK. Sure. But when I do that I have again a dependency on the concrete class, or? And it would be similar as knowing the class right from the start.
You don't. You have a dependency on the name of a class. The name could refer to two entirely different classes between invocations of FIND-CLASS. The name could also come from an external source. Thus, this is purely a run-time dependency and it would be quite possible that FIND-CLASS returns NIL if given a symbol that does not designate a currently-defined class.
Le 07/02/2021 à 10:10, Marco Antoniotti a écrit :
... and I can always CHANGE-CLASS, can't I?
Of course. There are a lot of features of CL that renders most of the software engineering fad entirely useless or obvious.
This is part of the reasons why lisp is not more popular: there's no PhD to work on when you work with lisp, since the problems they solve don't occur or are solved trivially in lisp; there's no software engineering technique to be developed and evangelized (with books or expensive conferences) when you work with lisp because there are no deficiencies so dire in the language to require them; there's no need for sophisticated tools and IDE to be developped and sold to lisp programmers, since the syntax is so trivial just a few hacks in emacs are enough for 99% of the needs.
Am 07.02.2021 um 11:28 schrieb Pascal Bourguignon pjb@informatimago.com:
Le 07/02/2021 à 10:10, Marco Antoniotti a écrit :
... and I can always CHANGE-CLASS, can't I?
Of course. There are a lot of features of CL that renders most of the software engineering fad entirely useless or obvious.
This is part of the reasons why lisp is not more popular: there's no PhD to work on when you work with lisp, since the problems they solve don't occur or are solved trivially in lisp; there's no software engineering technique to be developed and evangelized (with books or expensive conferences) when you work with lisp because there are no deficiencies so dire in the language to require them; there's no need for sophisticated tools and IDE to be developped and sold to lisp programmers, since the syntax is so trivial just a few hacks in emacs are enough for 99% of the needs.
I’ve come to appreciate this during the last 2 years since I’ve started with Common Lisp. Coming from 20 years of static types this is quite a difference. I tend to think too complicated. But most of the things that are more simple stem from the dynamic nature IMO. To some extend this (CHANGE-CLASS, etc.) is also possible in Python, Clojure or Groovy. Though it’s really simple and convenient in Common Lisp. I also do Scala development in Emacs. So this sophistication (and maybe bloat) is not always necessary. But IDEs like IntelliJ are really very specialized and are super productive. Maybe just trying to mimick what Common Lisp can do with far simpler tooling. Statically typed systems also have some benefits. Like, when all compiles you have some guaranties about the soundness of your type model in the system. Some of the technologies (like dependency injection) are geared towards making things simpler in large projects where you have different teams working in different parts of the application and interfaces in a statically typed language.
Manfred
Ooooh you are soooo evil Pascal :) :) :)
On Sun, Feb 7, 2021 at 11:29 AM Pascal Bourguignon pjb@informatimago.com wrote:
Le 07/02/2021 à 10:10, Marco Antoniotti a écrit :
... and I can always CHANGE-CLASS, can't I?
Of course. There are a lot of features of CL that renders most of the software engineering fad entirely useless or obvious.
This is part of the reasons why lisp is not more popular: there's no PhD to work on when you work with lisp, since the problems they solve don't occur or are solved trivially in lisp; there's no software engineering technique to be developed and evangelized (with books or expensive conferences) when you work with lisp because there are no deficiencies so dire in the language to require them; there's no need for sophisticated tools and IDE to be developped and sold to lisp programmers, since the syntax is so trivial just a few hacks in emacs are enough for 99% of the needs.
-- __Pascal Bourguignon__
Am 07.02.2021 um 09:45 schrieb Hans Hübner hans.huebner@gmail.com:
Am So., 7. Feb. 2021 um 09:35 Uhr schrieb Manfred Bergmann manfred.bergmann@me.com:
Am 07.02.2021 um 09:23 schrieb Hans Hübner hans.huebner@gmail.com:
Am So., 7. Feb. 2021 um 09:20 Uhr schrieb Manfred Bergmann manfred.bergmann@me.com: But fundamentally, you also can’t create an instance of a class in Common Lisp from just the symbol. The class definition must be known.
This simply is untrue. You can use FIND-CLASS to find a class named by a symbol and then instantiate it.
Well, OK. Sure. But when I do that I have again a dependency on the concrete class, or? And it would be similar as knowing the class right from the start.
You don't. You have a dependency on the name of a class. The name could refer to two entirely different classes between invocations of FIND-CLASS. The name could also come from an external source. Thus, this is purely a run-time dependency and it would be quite possible that FIND-CLASS returns NIL if given a symbol that does not designate a currently-defined class.
OK, nice. This is probably why make-instance `foo can work instead of this boilerplate Abstract Factory in Java. Actually looks like FIND-CLASS is the simple version of dependency-injection.
Manfred
Le 06/02/2021 à 22:37, Manfred Bergmann a écrit :
You have a class. This class uses some other class. But by using or creating an instance of this other class directly you create a dependency on something concrete. That’s not what you want, because you might want to replace this with something else if required. For example with a mock or fake implementation in a test. ‚Dependency injection‘ allows you to declare this dependency with just an interface/protocol and have some other facility (the dependency injection framework) ‚inject‘ a concrete object at run-time. A similar thing could certainly be done by just using a constructor parameter (strategy pattern). But I think the important part here is the dependency on just an interface and not on a concrete implementation. For flexibility.
With some code:
;;;------------------------------------------------------------
(defclass used () ())
(defmethod used-stuff ((self used)) 'stuff)
;;; ---
(defclass user () ((used :reader used)))
(defmethod initialize-instance :after ((self user) &key &allow-other-keys) (setf (slot-value self 'used) (make-instance 'used #|OOPS, Dependency!|#)))
(defmethod user-stuff ((self user)) ;; Not a real dependency on the used class, ;; it's a dependency on the used-stuff generic function (interface). (used-stuff (used self)))
;;; ---
(defclass client () ())
(defmethod create-user ((self client)) ;; The class client depends directly on the user class, ;; and indirectly on the used class. (make-instance 'user))
;;;------------------------------------------------------------
(defclass used () ())
(defmethod used-stuff ((self used)) 'stuff)
;;; ---
(defclass user () ((used :initarg :used :reader used)))
;; The user class has no more any dependency on the used class.
(defmethod user-stuff ((self user)) ;; Not a real dependency on the used class, ;; it's a dependency on the used-stuff generic function (interface). (used-stuff (used self)))
;;; ---
(defclass client () ())
(defmethod create-user ((self client)) ;; The class client depends explicitely on the user and used classes. ;; But now, the class user doesn't depend directly on the used class; ;; this dependency is injected by the client into the user classe: (make-instance 'user :used (make-instance 'used)))
;;;------------------------------------------------------------
;; Notably if the client wants the user to use another used class:
(defclass variant-used (used) ()) (defmethod used-stuff ((self variant-used)) 'variant-stuff)
(defmethod create-user ((self client)) ;; only the client needs to be changed; the user class won't know ;; the difference: (make-instance 'user :used (make-instance 'variant-used)))
OK.
procedure foo is x : Integer := 42; begin ... end
Now foo depends on the hardwired '42' (as is should, one may argue :) ). And we are not even talking about "classes" or Lisp here.
Have I boiled down to the essentials? How do you do the rolling eyes emoticon?
All the best
Marco
On Sat, Feb 6, 2021 at 10:50 PM Pascal Bourguignon pjb@informatimago.com wrote:
Le 06/02/2021 à 22:37, Manfred Bergmann a écrit :
You have a class. This class uses some other class. But by using or creating an instance of this other class directly you
create a dependency on something concrete.
That’s not what you want, because you might want to replace this with
something else if required. For example with a mock or fake implementation in a test.
‚Dependency injection‘ allows you to declare this dependency with just
an interface/protocol and have some other facility (the dependency injection framework) ‚inject‘ a concrete object at run-time.
A similar thing could certainly be done by just using a constructor
parameter (strategy pattern).
But I think the important part here is the dependency on just an
interface and not on a concrete implementation. For flexibility.
With some code:
;;;------------------------------------------------------------
(defclass used () ())
(defmethod used-stuff ((self used)) 'stuff)
;;; ---
(defclass user () ((used :reader used)))
(defmethod initialize-instance :after ((self user) &key &allow-other-keys) (setf (slot-value self 'used) (make-instance 'used #|OOPS, Dependency!|#)))
(defmethod user-stuff ((self user)) ;; Not a real dependency on the used class, ;; it's a dependency on the used-stuff generic function (interface). (used-stuff (used self)))
;;; ---
(defclass client () ())
(defmethod create-user ((self client)) ;; The class client depends directly on the user class, ;; and indirectly on the used class. (make-instance 'user))
;;;------------------------------------------------------------
(defclass used () ())
(defmethod used-stuff ((self used)) 'stuff)
;;; ---
(defclass user () ((used :initarg :used :reader used)))
;; The user class has no more any dependency on the used class.
(defmethod user-stuff ((self user)) ;; Not a real dependency on the used class, ;; it's a dependency on the used-stuff generic function (interface). (used-stuff (used self)))
;;; ---
(defclass client () ())
(defmethod create-user ((self client)) ;; The class client depends explicitely on the user and used classes. ;; But now, the class user doesn't depend directly on the used class; ;; this dependency is injected by the client into the user classe: (make-instance 'user :used (make-instance 'used)))
;;;------------------------------------------------------------
;; Notably if the client wants the user to use another used class:
(defclass variant-used (used) ()) (defmethod used-stuff ((self variant-used)) 'variant-stuff)
(defmethod create-user ((self client)) ;; only the client needs to be changed; the user class won't know ;; the difference: (make-instance 'user :used (make-instance 'variant-used)))
-- __Pascal Bourguignon__
Le 07/02/2021 à 08:58, Marco Antoniotti a écrit :
OK.
procedure foo is x : Integer := 42; begin ... end
Now foo depends on the hardwired '42' (as is should, one may argue :) ). And we are not even talking about "classes" or Lisp here.
Have I boiled down to the essentials?
Yes. Notably if, depending on the language, 42 is an object, and you may have a reason to run (foo) with a different object (either 33, or a (Debugging-Integer 42) or a (Bigint-Optimized-Integer 42).
Fundamentally, the direct dependency of user -> used is not a problem, since you can always modify the source if you want to change the dependency.
So the point of dependency injection is to be able to change this dependency without changing the source. This need occurs notably when you want to run unit tests, ie. you want to test a single class (unit) without using the other classes. Instead, you will provide stub implementations of the other classes.
Ie. in practice, you will have:
(defvar *used-stuff-called* nil) (defclass stub-used (used) ())
(defmethod used-stuff ((self variant-used)) (setf *used-stuff-called* t) 'stuff)
(defmethod test/user ((self client)) (let ((user (make-instance 'user :used (make-instance 'stub-used))) (*used-stuff-called* nil)) (assert (eq 'stuff (user-stuff user))) (assert *used-stuff-called*)))
How do you do the rolling eyes emoticon?
🙄
All the best
Marco
On Sat, Feb 6, 2021 at 10:50 PM Pascal Bourguignon <pjb@informatimago.com mailto:pjb@informatimago.com> wrote:
Le 06/02/2021 à 22:37, Manfred Bergmann a écrit : > You have a class. This class uses some other class. > But by using or creating an instance of this other class directly you create a dependency on something concrete. > That’s not what you want, because you might want to replace this with something else if required. For example with a mock or fake implementation in a test. > ‚Dependency injection‘ allows you to declare this dependency with just an interface/protocol and have some other facility (the dependency injection framework) ‚inject‘ a concrete object at run-time. > A similar thing could certainly be done by just using a constructor parameter (strategy pattern). > But I think the important part here is the dependency on just an interface and not on a concrete implementation. For flexibility. With some code: ;;;------------------------------------------------------------ (defclass used () ()) (defmethod used-stuff ((self used)) 'stuff) ;;; --- (defclass user () ((used :reader used))) (defmethod initialize-instance :after ((self user) &key &allow-other-keys) (setf (slot-value self 'used) (make-instance 'used #|OOPS, Dependency!|#))) (defmethod user-stuff ((self user)) ;; Not a real dependency on the used class, ;; it's a dependency on the used-stuff generic function (interface). (used-stuff (used self))) ;;; --- (defclass client () ()) (defmethod create-user ((self client)) ;; The class client depends directly on the user class, ;; and indirectly on the used class. (make-instance 'user)) ;;;------------------------------------------------------------ (defclass used () ()) (defmethod used-stuff ((self used)) 'stuff) ;;; --- (defclass user () ((used :initarg :used :reader used))) ;; The user class has no more any dependency on the used class. (defmethod user-stuff ((self user)) ;; Not a real dependency on the used class, ;; it's a dependency on the used-stuff generic function (interface). (used-stuff (used self))) ;;; --- (defclass client () ()) (defmethod create-user ((self client)) ;; The class client depends explicitely on the user and used classes. ;; But now, the class user doesn't depend directly on the used class; ;; this dependency is injected by the client into the user classe: (make-instance 'user :used (make-instance 'used))) ;;;------------------------------------------------------------ ;; Notably if the client wants the user to use another used class: (defclass variant-used (used) ()) (defmethod used-stuff ((self variant-used)) 'variant-stuff) (defmethod create-user ((self client)) ;; only the client needs to be changed; the user class won't know ;; the difference: (make-instance 'user :used (make-instance 'variant-used))) -- __Pascal Bourguignon__
-- Marco Antoniotti, Associate Professortel.+39 - 02 64 48 79 01 DISCo, Università Milano Bicocca U14 2043http://bimib.disco.unimib.it http://bimib.disco.unimib.it/ Viale Sarca 336 I-20126 Milan (MI) ITALY
DI Frameworks live on a different plane than most of the micro-patterns in the GoF class: They try to solve the problem that when using a framework, composing the components that a specific framework application needs can be difficult and complex. This is done by annotating the application component that makes use of a framework feature (using configuration files or, more recently, source code annotations), and then having the framework runtime collect the dependencies from the annotations to build the right set of components. Effectively, instead of statically linking the application to the framework, the linking is performed at run-time and, as the linking information is richer than in traditional linking systems (i.e. an application-level annotation instead of just a function name), it can do a lot of things that are typically aspects of systems written in dynamically typed programming languages.
The applicability of dependency injection really depends on the complexity of the framework that is being talked about. In simple, and in particular in framework-less systems, one does not really need a framework-specific, configurable, run-time linking component. Most Lisp systems do not actually depend on a framework, and Lisp - as a dynamic language - provides for plenty of run-time features that can be used to achieve what is the magic sauce in dependency injection frameworks. One may want to standardize how component linkage is performed in a large Lisp system, of course, but the challenges would be different as much of the tooling to perform the actual work is available right away.
The same can be said about many of the patterns that are used in the OO and Java world, of course. They often provide solution architectures to problems that are hard to solve given the constraints of - in particular - Java, and for which trivially easy solutions can be devised in Lisp on the fly without having to resort to a named pattern.
-Hans
Am So., 7. Feb. 2021 um 08:59 Uhr schrieb Marco Antoniotti < marco.antoniotti@unimib.it>:
OK.
procedure foo is x : Integer := 42; begin ... end
Now foo depends on the hardwired '42' (as is should, one may argue :) ). And we are not even talking about "classes" or Lisp here.
Have I boiled down to the essentials? How do you do the rolling eyes emoticon?
All the best
Marco
On Sat, Feb 6, 2021 at 10:50 PM Pascal Bourguignon pjb@informatimago.com wrote:
Le 06/02/2021 à 22:37, Manfred Bergmann a écrit :
You have a class. This class uses some other class. But by using or creating an instance of this other class directly you
create a dependency on something concrete.
That’s not what you want, because you might want to replace this with
something else if required. For example with a mock or fake implementation in a test.
‚Dependency injection‘ allows you to declare this dependency with just
an interface/protocol and have some other facility (the dependency injection framework) ‚inject‘ a concrete object at run-time.
A similar thing could certainly be done by just using a constructor
parameter (strategy pattern).
But I think the important part here is the dependency on just an
interface and not on a concrete implementation. For flexibility.
With some code:
;;;------------------------------------------------------------
(defclass used () ())
(defmethod used-stuff ((self used)) 'stuff)
;;; ---
(defclass user () ((used :reader used)))
(defmethod initialize-instance :after ((self user) &key &allow-other-keys) (setf (slot-value self 'used) (make-instance 'used #|OOPS, Dependency!|#)))
(defmethod user-stuff ((self user)) ;; Not a real dependency on the used class, ;; it's a dependency on the used-stuff generic function (interface). (used-stuff (used self)))
;;; ---
(defclass client () ())
(defmethod create-user ((self client)) ;; The class client depends directly on the user class, ;; and indirectly on the used class. (make-instance 'user))
;;;------------------------------------------------------------
(defclass used () ())
(defmethod used-stuff ((self used)) 'stuff)
;;; ---
(defclass user () ((used :initarg :used :reader used)))
;; The user class has no more any dependency on the used class.
(defmethod user-stuff ((self user)) ;; Not a real dependency on the used class, ;; it's a dependency on the used-stuff generic function (interface). (used-stuff (used self)))
;;; ---
(defclass client () ())
(defmethod create-user ((self client)) ;; The class client depends explicitely on the user and used classes. ;; But now, the class user doesn't depend directly on the used class; ;; this dependency is injected by the client into the user classe: (make-instance 'user :used (make-instance 'used)))
;;;------------------------------------------------------------
;; Notably if the client wants the user to use another used class:
(defclass variant-used (used) ()) (defmethod used-stuff ((self variant-used)) 'variant-stuff)
(defmethod create-user ((self client)) ;; only the client needs to be changed; the user class won't know ;; the difference: (make-instance 'user :used (make-instance 'variant-used)))
-- __Pascal Bourguignon__
-- Marco Antoniotti, Associate Professor tel. +39 - 02 64 48 79 01 DISCo, Università Milano Bicocca U14 2043 http://bimib.disco.unimib.it Viale Sarca 336 I-20126 Milan (MI) ITALY
Am 07.02.2021 um 09:25 schrieb Hans Hübner hans.huebner@gmail.com:
The applicability of dependency injection really depends on the complexity of the framework that is being talked about. In simple, and in particular in framework-less systems, one does not really need a framework-specific, configurable, run-time linking component. Most Lisp systems do not actually depend on a framework, and Lisp - as a dynamic language - provides for plenty of run-time features that can be used to achieve what is the magic sauce in dependency injection frameworks. One may want to standardize how component linkage is performed in a large Lisp system, of course, but the challenges would be different as much of the tooling to perform the actual work is available right away.
Well. There are plenty libraries out there for Common Lisp. The question one has to ask. Do I use them directly, creating a source code dependency, like a web server. Or do create and use an abstraction in order to be able to replace it. In the sense of ‚keeping libraries at arms length‘. For small application this is of course overkill. But I would be interested what the alternatives in Lisp are. In some Common Lisp project I did last year I had separated the protocols from the implementations. Structured the ASDF loading in a sequence that the protocols are loaded first, then a layer that uses those protocols is loaded, and finally the concrete implementations are loaded. This worked fine. But I just came up with this and I’m not sure if there are better alternatives.
The same can be said about many of the patterns that are used in the OO and Java world, of course. They often provide solution architectures to problems that are hard to solve given the constraints of - in particular - Java, and for which trivially easy solutions can be devised in Lisp on the fly without having to resort to a named pattern.
That’s what I’m trying to figure out. Implementing Abstract Factory in Java (or Scala for what I do) is relatively bloated. We figured so far that make-instance `clazz-symbol is probably the simplified version to do that in Common Lisp, with maybe a little bit of boiler-plate.
Manfred
Am So., 7. Feb. 2021 um 08:59 Uhr schrieb Marco Antoniotti marco.antoniotti@unimib.it: OK.
procedure foo is x : Integer := 42; begin ... end
Now foo depends on the hardwired '42' (as is should, one may argue :) ). And we are not even talking about "classes" or Lisp here.
Have I boiled down to the essentials? How do you do the rolling eyes emoticon?
All the best
Marco
On Sat, Feb 6, 2021 at 10:50 PM Pascal Bourguignon pjb@informatimago.com wrote: Le 06/02/2021 à 22:37, Manfred Bergmann a écrit :
You have a class. This class uses some other class. But by using or creating an instance of this other class directly you create a dependency on something concrete. That’s not what you want, because you might want to replace this with something else if required. For example with a mock or fake implementation in a test. ‚Dependency injection‘ allows you to declare this dependency with just an interface/protocol and have some other facility (the dependency injection framework) ‚inject‘ a concrete object at run-time. A similar thing could certainly be done by just using a constructor parameter (strategy pattern). But I think the important part here is the dependency on just an interface and not on a concrete implementation. For flexibility.
With some code:
;;;------------------------------------------------------------
(defclass used () ())
(defmethod used-stuff ((self used)) 'stuff)
;;; ---
(defclass user () ((used :reader used)))
(defmethod initialize-instance :after ((self user) &key &allow-other-keys) (setf (slot-value self 'used) (make-instance 'used #|OOPS, Dependency!|#)))
(defmethod user-stuff ((self user)) ;; Not a real dependency on the used class, ;; it's a dependency on the used-stuff generic function (interface). (used-stuff (used self)))
;;; ---
(defclass client () ())
(defmethod create-user ((self client)) ;; The class client depends directly on the user class, ;; and indirectly on the used class. (make-instance 'user))
;;;------------------------------------------------------------
(defclass used () ())
(defmethod used-stuff ((self used)) 'stuff)
;;; ---
(defclass user () ((used :initarg :used :reader used)))
;; The user class has no more any dependency on the used class.
(defmethod user-stuff ((self user)) ;; Not a real dependency on the used class, ;; it's a dependency on the used-stuff generic function (interface). (used-stuff (used self)))
;;; ---
(defclass client () ())
(defmethod create-user ((self client)) ;; The class client depends explicitely on the user and used classes. ;; But now, the class user doesn't depend directly on the used class; ;; this dependency is injected by the client into the user classe: (make-instance 'user :used (make-instance 'used)))
;;;------------------------------------------------------------
;; Notably if the client wants the user to use another used class:
(defclass variant-used (used) ()) (defmethod used-stuff ((self variant-used)) 'variant-stuff)
(defmethod create-user ((self client)) ;; only the client needs to be changed; the user class won't know ;; the difference: (make-instance 'user :used (make-instance 'variant-used)))
-- __Pascal Bourguignon__
-- Marco Antoniotti, Associate Professor tel. +39 - 02 64 48 79 01 DISCo, Università Milano Bicocca U14 2043 http://bimib.disco.unimib.it Viale Sarca 336 I-20126 Milan (MI) ITALY
My 2c: It seems to me that too many things are getting mixed in (static/dynamic, abstract/concrete, etc). Dependency injection is only about dependencies and dependency inversion is also about abstraction. I would interpret dependency inversion as turning a private language of two parts into a public language that all parts depend on.For dependency injection, something like: A depends on B. Let's introduce a combinator C that depends on both (modified) A and B, which constructs a new object from mutually independent A and B. We can then use dependency inversion to make all of them use a public language, so that C can be reused for different As and Bs.
On Sun, Feb 7, 2021 at 9:27, Hans Hübnerhans.huebner@gmail.com wrote: DI Frameworks live on a different plane than most of the micro-patterns in the GoF class: They try to solve the problem that when using a framework, composing the components that a specific framework application needs can be difficult and complex. This is done by annotating the application component that makes use of a framework feature (using configuration files or, more recently, source code annotations), and then having the framework runtime collect the dependencies from the annotations to build the right set of components. Effectively, instead of statically linking the application to the framework, the linking is performed at run-time and, as the linking information is richer than in traditional linking systems (i.e. an application-level annotation instead of just a function name), it can do a lot of things that are typically aspects of systems written in dynamically typed programming languages. The applicability of dependency injection really depends on the complexity of the framework that is being talked about. In simple, and in particular in framework-less systems, one does not really need a framework-specific, configurable, run-time linking component. Most Lisp systems do not actually depend on a framework, and Lisp - as a dynamic language - provides for plenty of run-time features that can be used to achieve what is the magic sauce in dependency injection frameworks. One may want to standardize how component linkage is performed in a large Lisp system, of course, but the challenges would be different as much of the tooling to perform the actual work is available right away. The same can be said about many of the patterns that are used in the OO and Java world, of course. They often provide solution architectures to problems that are hard to solve given the constraints of - in particular - Java, and for which trivially easy solutions can be devised in Lisp on the fly without having to resort to a named pattern. -Hans Am So., 7. Feb. 2021 um 08:59 Uhr schrieb Marco Antoniotti marco.antoniotti@unimib.it:
OK. procedure foo is x : Integer := 42;begin ... end
Now foo depends on the hardwired '42' (as is should, one may argue :) ). And we are not even talking about "classes" or Lisp here.
Have I boiled down to the essentials? How do you do the rolling eyes emoticon? All the best Marco
On Sat, Feb 6, 2021 at 10:50 PM Pascal Bourguignon pjb@informatimago.com wrote:
Le 06/02/2021 à 22:37, Manfred Bergmann a écrit :
You have a class. This class uses some other class. But by using or creating an instance of this other class directly you create a dependency on something concrete. That’s not what you want, because you might want to replace this with something else if required. For example with a mock or fake implementation in a test. ‚Dependency injection‘ allows you to declare this dependency with just an interface/protocol and have some other facility (the dependency injection framework) ‚inject‘ a concrete object at run-time. A similar thing could certainly be done by just using a constructor parameter (strategy pattern). But I think the important part here is the dependency on just an interface and not on a concrete implementation. For flexibility.
With some code:
;;;------------------------------------------------------------
(defclass used () ())
(defmethod used-stuff ((self used)) 'stuff)
;;; ---
(defclass user () ((used :reader used)))
(defmethod initialize-instance :after ((self user) &key &allow-other-keys) (setf (slot-value self 'used) (make-instance 'used #|OOPS, Dependency!|#)))
(defmethod user-stuff ((self user)) ;; Not a real dependency on the used class, ;; it's a dependency on the used-stuff generic function (interface). (used-stuff (used self)))
;;; ---
(defclass client () ())
(defmethod create-user ((self client)) ;; The class client depends directly on the user class, ;; and indirectly on the used class. (make-instance 'user))
;;;------------------------------------------------------------
(defclass used () ())
(defmethod used-stuff ((self used)) 'stuff)
;;; ---
(defclass user () ((used :initarg :used :reader used)))
;; The user class has no more any dependency on the used class.
(defmethod user-stuff ((self user)) ;; Not a real dependency on the used class, ;; it's a dependency on the used-stuff generic function (interface). (used-stuff (used self)))
;;; ---
(defclass client () ())
(defmethod create-user ((self client)) ;; The class client depends explicitely on the user and used classes. ;; But now, the class user doesn't depend directly on the used class; ;; this dependency is injected by the client into the user classe: (make-instance 'user :used (make-instance 'used)))
;;;------------------------------------------------------------
;; Notably if the client wants the user to use another used class:
(defclass variant-used (used) ()) (defmethod used-stuff ((self variant-used)) 'variant-stuff)
(defmethod create-user ((self client)) ;; only the client needs to be changed; the user class won't know ;; the difference: (make-instance 'user :used (make-instance 'variant-used)))
Well, of course not. It’s just one implementation of ‚dependency inversion‘. How does it work in Common Lisp? I really don’t know. Well, I could imagine something. But I’d like to know what you had in mind.
Manfred
Am 06.02.2021 um 22:18 schrieb Scott McKay swmckay@gmail.com:
Nobody in the list community ever invented a fancy pants term like “dependency injection” because it’s so obvious how to do this that nobody thought to give it a name.
—Scott
On Feb 6, 2021, at 4:07 PM, Manfred Bergmann manfred.bergmann@me.com wrote:
Am 06.02.2021 um 21:44 schrieb Luís Oliveira luismbo@gmail.com:
On Sat, 6 Feb 2021 at 20:07, Rudi Araújo rudi.araujo@gmail.com wrote: Class::newInstance() doesn't have any parameters (also, it's deprecated: better to use getConstructor() or getDeclaredConstructor() and call newInstance() on it).
I guess this bit about getConstructor() explains why it'd be more convenient to use a Factory, or the Factory method pattern, or some dependency injection framework.
Yeah. Could be. But this constructor thingy could be hidden in a function similar as you would create a constructor function make-foo in Common Lisp. The reflection stuff is not considered a good practice in certain types of applications.
Dependency injection is about something else IMO. Well, Abstract Factory is about it, too, inversion of control. It allows you to create something without having to know the concrete type and without having to have a source dependency on it. In Common Lisp this could be solved easily by just separating a protocol from the implementation, maybe in separate packages.
Manfred
Manfred Bergmann manfred.bergmann@me.com wrote:
I am trying to discover design patterns and how they are implemented in Common Lisp.
Hello,
you may be interested in those 2 articles that I wrote some time ago:
http://www.jucs.org/jucs_16_2/revisiting_the_visitor_the http://www.jucs.org/jucs_14_20/binary_methods_programming_the
Nothing revolutionary, but they're hopefully quite pedagogical.
Le 06/02/2021 à 14:45, Manfred Bergmann a écrit :
Hello.
I am trying to discover design patterns and how they are implemented in Common Lisp.
Using defmacro.
Have a look at: http://groups.google.com/group/comp.lang.lisp/msg/ee09f8475bc7b2a0 http://groups.google.com/group/comp.programming/msg/9e7b8aaec1794126
According to Peter Norvig (http://norvig.com/design-patterns/design-patterns.pdf) and this source (https://wiki.c2.com/?AreDesignPatternsMissingLanguageFeatures) some design pattern are more simple in Common Lisp, or are actually not needed at all because the language can be extended to achieve the same thing: extendability, separation, abstraction.
Taking an Abstract Factory for example, the sources say that metaclasses can help do this. I’ve read a bit about metaclasses and the MOP but I fail to see how. I could probably come up with something.
A GUI framework for example. I could create something like this :
— (defclass meta-button (standard-class) ((gui-button-backend :initform nil :accessor gui-button-backend)))
(defmethod closer-mop:validate-superclass ((class meta-button) (superclass standard-class)) t)
(defclass button () () (:metaclass meta-button)) (defun make-button () (make-instance 'button)) —
This code is incomplete. It doesn't specify the concrete gui-button-backend, and how it's used to make an instance of button.
Also, it doesn't really implement the abstract factory design pattern, because defclass being a macro, that specifies that the metaclass IS the name of the metaclass, not that it is EVALUATED to a name of a metaclass, you cannot create a class with a metaclass in a variable (not directly at least; you could do it using eval and building a defclass form at run-time). Fundamentally defclass is a compilation-time operator, while the abstract factory design pattern is designed to select the concrete classes at run-time.
And configure a QT or GTK, Motif (or whatever) button backend to the metaclass on startup (or so). But I think this would just be the pattern somehow turned into a metaclass. Doesn’t feel like the right thing.
Does anyone know what Peter Norvig had in mind when he said that metaclasses can be used instead of Abstract Factory pattern?
As an abstraction mechanism, metaclasses can probably help to implement some design patterns. However, the code presented above doesn't demonstrate it.
That said, I won't try to define such a metaclass, because in lisp there are easier ways to define the abstract factory design pattern.
First, let's remember that design patterns were invented to paper over deficiencies in OO programming languages. Lisp doesn't have a lot of deficiencies, and it has defmacro which let you add features to the lisp language easily.
Next, concerning the Asbtract Factory design pattern, it seems to me that an important part of this design pattern is the abstract classes of the factory, and of the concrete classes. Abstract classes are important in a number of OO programming languages because 1) methods are attached to classes, and 2) they are a way to define interfaces.
However, some OO programming languages have other abstractions to define interfaces (eg. @protocols in Objective-C), or just don't need an abstract superclass to have a bunch of concrete classes implementing the same set of methods (or even methods implemented generically for all of them). OO programming languages that allow duck typing such as Python for example, or indeed, Common Lisp CLOS where the methods are attached to generic functions, not to classes and where all classes are subclass of the standard-object class.
So when you remove the abstract superclasses, it remains only the parameterisation of the concrete classes that must be created.
In the case of CLOS, the MAKE-INSTANCE generic function already takes the class as parameter!!! There is absolutely no need for an asbtract factory design pattern in CLOS, because CLOS doesn't have the deficiency in the first place!
There's a last aspect of that design pattern (all design patterns actually), which is the participants and collaboration structure. But note that in the case of the Abstract Factory design pattern, the description given is actually a meta design pattern, because the participants and collaborations are given as a mere example, not in a concrete form. When you actually implement the design patter, you will choose the exact set of participants (the concrete classes) and their relationships. So the question is how you want to represent this aspect.
It can be just a variable containing the class. Perhaps it could be a list of classes. Or a structure or clos object with a set of classes in slots. Or a hash-table or other map such as a-list.
Of course, you can also define a class, and define a bunch of methods returning the concrete class names. There would be little reason to do that, unless for some reason this class would need attributes (slots) used to compute the concrete class names in the various methods of the various generic functions.
Let's also remember that you can specialize generic function on specific lisp objects, not only on classes.
To recapitulate, here are various examples of code:
(defclass x11-window () ((title :initarg :title) (subviews :initarg :subviews))) (defclass x11-pane () ()) (defclass x11-button () ((title :initarg :title))) (defclass x11-textfield () ((value :initarg :value)))
;;; Using a mere variable to store the concrete class:
(defparameter *button-class* 'button)
(defun configure (backend) (ecase backend (x11 (setf *button-class* 'x11-button)) (macos (setf *button-class 'macos-button)) ...))
(make-instance *button-class* :title "OK")
;;; Of course you can define a bunch of variables for several concrete classes.
;;; Using a map (here an a-list) for several concrete classes:
(defparameter *gui-factory* '((window . x11-window) (pane . x11-pane) (button . x11-button) (textfield . x11-textfield)))
(defun aget (alist key) (cdr (assoc key alist)))
(defun window-class (factory) (aget factory 'window)) (defun pane-class (factory) (aget factory 'pane)) (defun button-class (factory) (aget factory 'button)) (defun textfield-class (factory) (aget factory 'textfield))
(make-instance (window-class *gui-factory*) :title "Example Window" :subviews (list (make-instance (textfield-class *gui-factory*) :value "Is it a good example?") (make-instance (button-class *gui-factory*) :title "Yep") (make-instance (button-class *gui-factory*) :title "Nope")))
;;; Using a structure (or a clos object, it would be similar):
(defclass classes () ((window :initarg :window :reader window-class) (pane :initarg :pane :reader pane-class) (button :initarg :button :reader button-class) (textfield :initarg :textfield :reader textfield-class)))
(defparameter *gui-factory* (make-instance 'classes :window 'x11-window :pane 'x11-pane :button 'x11-button :textfield 'x11-textfield))
(make-instance (window-class *gui-factory*) :title "Example Window" :subviews (list (make-instance (textfield-class *gui-factory*) :value "Is it a good example?") (make-instance (button-class *gui-factory*) :title "Yep") (make-instance (button-class *gui-factory*) :title "Nope")))
;; It's actually the same as with the map, in both cases, instead of ;; using aget or structure or clos instance accessors, we can define a ;; function implementing a common API.
;;; Using methods:
(defgeneric window-class (factory)) (defgeneric pane-class (factory)) (defgeneric button-class (factory)) (defgeneric textfield-class (factory))
;; the you can define an abstract factory class, and implement the methods:
(defclass x11-factory () ())
(defmethod window-class ((factory x11-factory)) 'x11-window) (defmethod pane-class ((factory x11-factory)) 'x11-pane) (defmethod button-class ((factory x11-factory)) 'x11-button) (defmethod textfield-class ((factory x11-factory)) 'x11-textfield)
(defparameter *gui-factory* (make-instance 'x11-factory))
(make-instance (window-class *gui-factory*) :title "Example Window" :subviews (list (make-instance (textfield-class *gui-factory*) :value "Is it a good example?") (make-instance (button-class *gui-factory*) :title "Yep") (make-instance (button-class *gui-factory*) :title "Nope")))
;; But as you can see, the methods are constant functions, the classes ;; are hard-coded. So we could avoid the useless factory class, and ;; dispatch only on a value, for example a symbol:
(defmethod window-class ((factory (eql 'x11))) 'x11-window) (defmethod pane-class ((factory (eql 'x11))) 'x11-pane) (defmethod button-class ((factory (eql 'x11))) 'x11-button) (defmethod textfield-class ((factory (eql 'x11))) 'x11-textfield)
(defparameter *gui-factory* 'x11)
(make-instance (window-class *gui-factory*) :title "Example Window" :subviews (list (make-instance (textfield-class *gui-factory*) :value "Is it a good example?") (make-instance (button-class *gui-factory*) :title "Yep") (make-instance (button-class *gui-factory*) :title "Nope")))
;; As you can see, no difference.
Now, where would defmacro enter the scene? We could define a few macros and supporting functions to be able to define factories with their set of concrete classes; the macro would expand to the boilerplate code, such as the generic and methods, according to the implementation choice (alist, factory class, factory symbol, etc).
(define-abstract-factory gui-factory (window pane button textfield))
(define-factory x11-factory gui-factory (x11-window x11-pane x11-button x11-textfield))
(define-factory x11-factory gui-factory (macos-window macos-pane macos-button macos-textfield))
(defparameter *gui-factory* (make-factory 'x11-factory))
(make-instance (window-class *gui-factory*) :title "Example Window" :subviews (list (make-instance (textfield-class *factory*) :value "Is it a good example?") (make-instance (button-class *factory*) :title "Yep") (make-instance (button-class *factory*) :title "Nope")))
(define-abstract-factory enterprise-factory (employee invoice tax))
(define-factory incorporated-enterprise (enterprise-factory) (inc-employee inc-invoice inc-tax))
(define-factory limited-enterprise (enterprise-factory) (ltd-employee ltd-invoice ltd-tax))
(defparameter *enterprise-factory* (make-factory 'incorporated-enterprise))
(make-instance (employee-class *enterprise-factory*) :name "John Doe"))
In effect, the abstract factory meta design pattern is thus implemented in lisp as a define-abstract-factory macro, and the concrete abstract factory design pattern as a define-factory macro.
Le 06/02/2021 à 17:35, Pascal Bourguignon a écrit :
Now, where would defmacro enter the scene? We could define a few macros and supporting functions to be able to define factories with their set of concrete classes; the macro would expand to the boilerplate code, such as the generic and methods, according to the implementation choice (alist, factory class, factory symbol, etc).
(define-abstract-factory gui-factory (window pane button textfield))
(define-factory x11-factory gui-factory (x11-window x11-pane x11-button x11-textfield))
(define-factory x11-factory gui-factory (macos-window macos-pane macos-button macos-textfield))
(defparameter *gui-factory* (make-factory 'x11-factory))
[...]
In effect, the abstract factory meta design pattern is thus implemented in lisp as a define-abstract-factory macro, and the concrete abstract factory design pattern as a define-factory macro.
I should add that while defining such macros may seem useless given the small about of boilerplate they would cover, they offer the advantage of providing an abstraction, that would let you change easily the implementation at a later time (either to add features, or for optimization reasons, etc).
I completely agree that most design patterns are the result of deficiencies in most OO languages and could be solved with Lisp easily, usually with defmacro. I'd also like to add what the author of one of my favorite books had to say on the topic:
Design patterns are purported "master programmer advice" strongly suggesting to young or otherwise impressionable programmers that the design patterns convey important concepts, practices, or principles that have been "prethought." However, design patterns are not "master programmer advice!" Any master programmer would know that you cannot simply dish out a small number of generic solutions for generic situations, but that every situation is (potentially) different and warrants its own (potential) solution.
On Sat, Feb 6, 2021, at 11:42 AM, Pascal Bourguignon wrote:
Le 06/02/2021 à 17:35, Pascal Bourguignon a écrit :
Now, where would defmacro enter the scene? We could define a few macros and supporting functions to be able to define factories with their set of concrete classes; the macro would expand to the boilerplate code, such as the generic and methods, according to the implementation choice (alist, factory class, factory symbol, etc).
(define-abstract-factory gui-factory (window pane button textfield))
(define-factory x11-factory gui-factory (x11-window x11-pane x11-button x11-textfield))
(define-factory x11-factory gui-factory (macos-window macos-pane macos-button macos-textfield))
(defparameter *gui-factory* (make-factory 'x11-factory))
[...]
In effect, the abstract factory meta design pattern is thus implemented in lisp as a define-abstract-factory macro, and the concrete abstract factory design pattern as a define-factory macro.
I should add that while defining such macros may seem useless given the small about of boilerplate they would cover, they offer the advantage of providing an abstraction, that would let you change easily the implementation at a later time (either to add features, or for optimization reasons, etc).
-- __Pascal Bourguignon__
Of course. I guess many programmers come up with similar solutions without knowing any of the described and written down patterns. But I think it’s good to know them because by giving something a name it’s easier to talk about it.
Manfred
Am 06.02.2021 um 17:51 schrieb Michael Fiano mail@mfiano.net:
I completely agree that most design patterns are the result of deficiencies in most OO languages and could be solved with Lisp easily, usually with defmacro. I'd also like to add what the author of one of my favorite books had to say on the topic:
Design patterns are purported "master programmer advice" strongly suggesting to young or otherwise impressionable programmers that the design patterns convey important concepts, practices, or principles that have been "prethought." However, design patterns are not "master programmer advice!" Any master programmer would know that you cannot simply dish out a small number of generic solutions for generic situations, but that every situation is (potentially) different and warrants its own (potential) solution.
On Sat, Feb 6, 2021, at 11:42 AM, Pascal Bourguignon wrote:
Le 06/02/2021 à 17:35, Pascal Bourguignon a écrit :
Now, where would defmacro enter the scene? We could define a few macros and supporting functions to be able to define factories with their set of concrete classes; the macro would expand to the boilerplate code, such as the generic and methods, according to the implementation choice (alist, factory class, factory symbol, etc).
(define-abstract-factory gui-factory (window pane button textfield))
(define-factory x11-factory gui-factory (x11-window x11-pane x11-button x11-textfield))
(define-factory x11-factory gui-factory (macos-window macos-pane macos-button macos-textfield))
(defparameter *gui-factory* (make-factory 'x11-factory))
[...]
In effect, the abstract factory meta design pattern is thus implemented in lisp as a define-abstract-factory macro, and the concrete abstract factory design pattern as a define-factory macro.
I should add that while defining such macros may seem useless given the small about of boilerplate they would cover, they offer the advantage of providing an abstraction, that would let you change easily the implementation at a later time (either to add features, or for optimization reasons, etc).
-- __Pascal Bourguignon__
Hi.
Am 06.02.2021 um 17:42 schrieb Pascal Bourguignon pjb@informatimago.com:
Le 06/02/2021 à 17:35, Pascal Bourguignon a écrit :
Now, where would defmacro enter the scene? We could define a few macros and supporting functions to be able to define factories with their set of concrete classes; the macro would expand to the boilerplate code, such as the generic and methods, according to the implementation choice (alist, factory class, factory symbol, etc). (define-abstract-factory gui-factory (window pane button textfield)) (define-factory x11-factory gui-factory (x11-window x11-pane x11-button x11-textfield)) (define-factory x11-factory gui-factory (macos-window macos-pane macos-button macos-textfield)) (defparameter *gui-factory* (make-factory 'x11-factory))
[...] In effect, the abstract factory meta design pattern is thus implemented in lisp as a define-abstract-factory macro, and the concrete abstract factory design pattern as a define-factory macro.
I should add that while defining such macros may seem useless given the small about of boilerplate they would cover, they offer the advantage of providing an abstraction, that would let you change easily the implementation at a later time (either to add features, or for optimization reasons, etc).
Thanks for your thorough answer. Need a bit of time to fully go through it.
Manfred
:Pascal Bourguignon, speaking of some code being discussed
Also, it doesn't really implement the abstract factory design pattern, because defclass being a macro, that specifies that the metaclass IS the name of the metaclass, not that it is EVALUATED to a name of a metaclass, you cannot create a class with a metaclass in a variable (not directly at least; you could do it using eval and building a defclass form at run-time). Fundamentally defclass is a compilation-time operator, while the abstract factory design pattern is designed to select the concrete classes at run-time.
If you want to define "classes" at runtime rather than compile-time, you may want to use a prototype object system, rather than a class-based object system.
Yale T Scheme in 1981 had one, designed by Jonathan Rees. In Lisp, there have been Object Lisp (1985), ABCL (1986), and more recently Sheeple (2008), dto's CLON (that I can't find anymore), CLPOS (2015), and possibly many more. https://github.com/xach/sheeple https://github.com/DrPyser/Common-Lisp-Prototype-Object-System
I've been building my own prototype object orientation system for Gerbil Scheme, POO, and have been extremely happy using it for over a year. Precisely because I need to generate types or classes at runtime, since my runtime is my users' compile-time. I just added this week multiple inheritance to it with C3 linearization, and it's pretty cool. (I also built a similar system in nixpkgs.)
—♯ƒ • François-René ÐVB Rideau •Reflection&Cybernethics• http://fare.tunes.org Under democracy one party always devotes its chief energies to trying to prove that the other party is unfit to rule - and both commonly succeed, and are right. — H. L. Mencken