Re: Warum nimmt sum() keine strings?
Verfasst: Montag 12. November 2012, 11:43
Konsequenterweise hätte man eigentlich auch 'a' + 'b' verbieten müssen.
Seit 2002 Diskussionen rund um die Programmiersprache Python
https://www.python-forum.de/
Obwohl bereits alles gesagt ist, möchte ich darauf doch noch mal eingehen, denn die Aussage ist fundamental falsch. Konkatenation zeigt sich nicht darin, dass man plötzlich eine Stelle mehr hat (was Du wohl zu vermuten scheinst). Ich glaube, ein guter Teil Deiner Verwirrung ergibt sich daraus, dass Du nicht genau trennst zwischen der Zahl und der Darstellung der Zahl.mutetella hat geschrieben:Das Ergebnis von 1 + 2 ergibt eine andere Zahl, weil innerhalb der Möglichkeiten von 0 - 9 sich ein 1 + 2 durch die 3 darstellen lässt. Sind alle Möglichkeiten erschöpft, müssen wir auch bei Zahlen eine Konkatenation vornehmen, spätestens ab der 10.
Tatsächlich ist das auch ein Thema über das man sich vortrefflich streiten kann und viele Programmiersprachencommunities haben dazu unterschiedliche Ansichten.mkesper hat geschrieben:Konsequenterweise hätte man eigentlich auch 'a' + 'b' verbieten müssen.
Du meinst die Notation, oder? Denn eine Addition von strings gibt es ja nicht. Und auf Konkatenation kann ja nicht verzichtet werden...mkesper hat geschrieben:Konsequenterweise hätte man eigentlich auch 'a' + 'b' verbieten müssen.
Code: Alles auswählen
1 + 2 == 3
2 * 3 == 6
'a' + 'bc' == 'abc'
[1, 2, 3] + [4, 5] == [1, 2, 3, 4, 5]
set([1, 2, 3]) | set([3, 4]) == set([1, 2, 3, 4])
Code: Alles auswählen
(1 + 2) + 3 == 1 + (2 + 3)
(2 * 3) * 4 == 2 * (3 * 4)
('a' + 'bc') + 'def' == 'a' + ('bc' + 'def')
([1, 2, 3] + [4, 5]) + [6] == [1, 2, 3] + ([4, 5] + [6])
(set([1, 2, 3]) | set([3, 4])) | set([2]) == set([1, 2, 3]) | (set([3, 4]) | set([2]))
Code: Alles auswählen
3 + 0 = 3
4 * 1 == 4
'hallo' + '' == 'hallo'
[1, 2, 3] + [] == [1, 2, 3]
set([1, 2, 3]) | set() == set([1, 2, 3])
Code: Alles auswählen
def compose(f, g):
return lambda x: g(f(x))
class composable(object):
def __init__(self, function):
self.function = function
def __mul__(self, other):
return composable(compose(self.function, other))
def __call__(self, x):
return self.function(x)
@composable
def identity(x):
return x
@composable
def add4(x):
return x + 4
@composable
def mul3(x):
return x * 3
@composable
def pow2(x):
return x ** 2
u = add4 * mul3
v = add4 * identity
assert u(5) == 27
assert v(5) == add4(5)
a = (add4 * mul3) * pow2
b = add4 * (mul3 * pow2)
assert a(5) == b(5)
Code: Alles auswählen
def foo(n):
result = n + 3
return result, 'foo: {0} plus 3 ist {1}'.format(n, result)
def bar(n):
result = n * 2
return result, 'bar: {0} mal 2 ist {1}'.format(n, result)
Code: Alles auswählen
broken = compose(foo, bar)
print broken(5)
Code: Alles auswählen
((8, 'foo: 5 plus 3 ist 8', 8, 'foo: 5 plus 3 ist 8'), "bar: (8, 'foo: 5 plus 3 ist 8') mal 2 ist (8, 'foo: 5 plus 3 ist 8', 8, 'foo: 5 plus 3 ist 8')")
Code: Alles auswählen
foo: 5 plus 3 ist 8
bar: 8 mal 2 ist 16
Code: Alles auswählen
def logged_bind(logged_function, value, old_log):
result, new_log = logged_function(value)
log = '\n'.join([old_log, new_log])
return result, log
class logged_composable(composable):
def __mul__(self, other):
def composed(x, s=''):
y, t = logged_bind(self.function, x, s)
return logged_bind(other, y, t)
return logged_composable(composed)
@logged_composable
def foo(n):
result = n + 3
return result, 'foo: {0} plus 3 ist {1}'.format(n, result)
@logged_composable
def bar(n):
result = n * 2
return result, 'bar: {0} mal 2 ist {1}'.format(n, result)
baz = foo * bar
result, log = baz(5)
assert result == 16
print log
Code: Alles auswählen
def logged_bind(logged_function, value, old_log):
result, new_log = logged_function(value)
log = '\n'.join(s for s in [old_log, new_log] if s)
return result, log
@logged_composable
def logged_identity(x):
return x, ''
Code: Alles auswählen
a = bar * logged_identity
b = logged_identity * bar
c = bar
assert a(5) == b(5) == c(5)
Code: Alles auswählen
class LoggingMonad(object):
def __init__(self, value, log=''):
self.value = value
self.log = log
def __rshift__(self, function):
result = function(self.value)
log = '\n'.join(s for s in [self.log, result.log] if s)
return LoggingMonad(result.value, log)
class composable(composable):
def __mul__(self, other):
return composable(lambda x: self.function(x) >> other)
@staticmethod
@composable
def identity(x):
return LoggingMonad(x, '')
@LoggingMonad.composable
def foo(n):
result = n + 3
return LoggingMonad(result, 'foo: {0} plus 3 ist {1}'.format(n, result))
@LoggingMonad.composable
def bar(n):
result = n * 2
return LoggingMonad(result, 'bar: {0} mal 2 ist {1}'.format(n, result))
source = LoggingMonad(5)
target = source >> (foo * bar)
assert target.value == 16
print target.log
a = source >> (bar * LoggingMonad.identity)
b = source >> (LoggingMonad.identity * bar)
assert (a.value, a.log) == (b.value, b.log)
Ja, ich hatte dich da ungenau zitiert. Du nanntest die Praxistauglichkeit "nun ja - eher zweifelhaft"lunar hat geschrieben:Ich sage nicht, dass Monaden nicht besonders praxistauglich wären. Ich sage nur, dass manche Probleme, die man - gerade in der Haskell-Community - gerne mit Monaden löst, auch auf andere Weise gelöst werden können, gerade in nicht rein funktionalen Sprachen.
Es hängt IMO von beidem ab, der Implementierungssprache und der zu interpretierenden. Wenn letztere deklarativ ist, bietet sich die CPS-Variante an, weil das Environment, das duch die Continuations transportiert werden muss, dann "monoton" ist und leicht als Stack modelliert werden kann, und Continuations sind ja sowas wie ein selbst verwalteter "zukünftiger" call stack. Aber letztlich sind alle Lösungen äquivalent.lunar hat geschrieben:Der Interpreter ist ein gutes Beispiel: Eine CPS-Implementierung ist nicht die einzige Möglichkeit, [...] die Alternativen sind nicht minder gut, denn Eleganz ist letztlich auch eine Frage der Möglichkeiten, die eine Sprache bietet.
.Naja, ich schrieb dort (ich war der Antwortendelunar hat geschrieben:Das Beispiel auf Stack Overflow ist zwar nett, aber meines Erachtens nicht das Argument für Monaden (zumal der Antwortende dort die Sprache auch durch das Einfügen von schließenden Tags auch so modifiziert, dass sich der Parser auf sie anwenden lässt. Die ursprüngliche Frage zeigt keine schließenden Tags). Man kann die dort gezeigte Sprache auch kontextfrei parsen, und in einer zusätzlichen Iteration über den AST validieren. Das wäre nicht wesentlich komplizierter als ein monadischer Parser, aber halt vor allem in einer Sprache mit Zuständen.
Ja. Ich habe lange überlegt, welche Monaden man in Python brauchen könnte, und mehr als die Continuation Monade, monadische Parser-Kombinatoren und vielleicht die beschriebene Logging-Monade ist mir nicht eingefallen. Die List-Monade ist durch die list comprehension in Python zur Genüge abgedeckt, die Maybe/Error/Failure-Monaden durch Exceptions, None und Generatoren, die ungültige Werte herausfiltern, die IO-Monade durch print(), input() und open().lunar hat geschrieben:[...]ich glaube, dass man viele Probleme, die man mit Monaden löst, gar nicht hätte, wenn man eine weniger strenge und mehr pragmatische Sprache verwenden würde![]()
Das sehe ich ebenso. Allerdings macht es mich auch ratlos. Ich denke mir immer, die anderen Programmierer müssten das doch verstehen, wenn ich es sogar verstehe. Andererseits deutet das Fake-Zitat von Philip Wadler "a monad is a monoid in the category of endofunctors, what's the problem?" schon auf das Problem der Theoretiker hin, sich gegenüber Ottonormalprogrammierer verständlich zu machen. Als ich mit Monaden angefangen habe, habe ich auch erst lange nichts kapiert, weil es in diesem krausen math-speak daherkam und kein Monaden-Tutorial wirklich auf den wesentlichen Punkt kam, d.i. dass Monaden primär einen Kompositionsmechanismus für Nicht-Endofunktionen sind, also Pipelining von Funktionen vom Typ T-->T'.lunar hat geschrieben:Hinzu kommt, dass Monaden zur sinnvollen Anwendung einen gewissen theoretischen Hintergrund voraussetzen. Man muss mindestens die Bedeutung und den tieferen Sinn der Monaden-Gesetze verstanden haben, und schon das ist bestimmt nicht jedem Programmierer gegeben. Ich glaube auch, dass diese Komplexität eines der größten Hindernisse zur weiteren Verbreitung von Haskell ist, genau wie die theoretischen Aspekte früher der Verbreitung von LISP grenzen gesetzt haben.
Code: Alles auswählen
>>> sum(())
0
Code: Alles auswählen
>>> sum((1, 2, 3, 'hallo', 4))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Code: Alles auswählen
>>> 0 == 0.0 == 0j
True
>>> 0 == ''
False
Code: Alles auswählen
In [24]: xs
Out[24]: [['a'], ['b'], ['c']]
In [25]: sum(xs, [])
Out[25]: ['a', 'b', 'c']
Stimmt. Aber für Strings gibt es eine Alternative, um ungünstiges Laufzeitverhalten zu vermeiden - nämlich das in der Fehlermeldung beschriebene `.join()`. Für Listen und andere Objekte gibt es so etwas nicht. Ich stimme aber zu, dass diese künstliche Beschränkung merkwürdig ist. Wenn man `sum()` im streng mathematischen Sinn sehen möchte, dann sollte man IMHO ausschließlich Zahlen erlauben und zum Konkatenieren von vielen aufeinander folgenden Objekten eine separate Möglichkeit anbieten. Fände zumindest ich irgendwie besser.BlackJack hat geschrieben:Und das obwohl dieses Beispiel genau das gleiche ungünstige Laufzeitverhalten hat wie das Aufsummieren von Zeichenketten.
Um Don Knuth zu paraphrasieren: Beware of bugs in the above statements; I have only proved them correct, not tried them.BlackJack hat geschrieben:Code: Alles auswählen
In [24]: xs Out[24]: [['a'], ['b'], ['c']] In [25]: sum(xs, []) Out[25]: ['a', 'b', 'c']