programming - ruby server
¿Por qué Ruby tiene TrueClass y FalseClass en lugar de una sola clase booleana? (8)
Estaba trabajando en la serialización de valores cuando me enteré de este. Ruby tiene una clase TrueClass
y una clase FalseClass
, pero no tiene una clase Boolean
. Me gustaría saber por qué es esto.
Veo algunas ventajas al usar un Boolean
; por ejemplo, el análisis sintáctico de cadenas podría centralizarse en él.
Los desarrolladores de Ruby son más inteligentes que yo, así que debe haber muchas buenas razones que simplemente no veo. Pero en este momento me parece que tengo OneClass
y una TwoClass
lugar de Fixnum
.
Citando el foro Matz on Ruby (2013) :
... No hay nada verdadero y falso comúnmente compartido, por lo tanto no hay clase booleana. Además de eso, en Ruby, todo se comporta como valor booleano ...
Como otros han dicho, podrías "parchar" a Ruby. Crea tu propia clase. Aquí hay algo que se me ocurrió. Los métodos en la clase booleana son un poco tontos, pero podrían ser útiles programáticamente en algún momento.
class Boolean
def self.new(bool)
bool
end
def self.true
true
end
def self.false
false
end
end
class FalseClass
def is_a?(other)
other == Boolean || super
end
def self.===(other)
other == Boolean || super
end
end
class TrueClass
def is_a?(other)
other == Boolean || super
end
def self.===(other)
other == Boolean || super
end
end
Dado que todo, excepto false
y nil
evalúa como verdadero en Ruby de forma predeterminada, solo necesitaría agregar el análisis sintáctico a String.
Algo como esto podría funcionar:
class Object
## Makes sure any other object that evaluates to false will work as intended,
## and returns just an actual boolean (like it would in any context that expect a boolean value).
def trueish?; !!self; end
end
class String
## Parses certain strings as true; everything else as false.
def trueish?
# check if it''s a literal "true" string
return true if self.strip.downcase == ''true''
# check if the string contains a numerical zero
[:Integer, :Float, :Rational, :Complex].each do |t|
begin
converted_number = Kernel.send(t, self)
return false if converted_number == 0
rescue ArgumentError
# raises if the string could not be converted, in which case we''ll continue on
end
end
return false
end
end
Cuando se usa, esto te daría:
puts false.trueish? # => false
puts true.trueish? # => true
puts ''false''.trueish? # => false
puts ''true''.trueish? # => true
puts ''0''.trueish? # => false
puts ''1''.trueish? # => true
puts ''0.0''.trueish? # => false
puts ''1.0''.trueish? # => true
Creo que parte de la "gran idea" detrás de Ruby es hacer que el comportamiento que deseas sea inherente a tu programa (por ejemplo, análisis sintáctico booleano), en lugar de crear una clase completamente encapsulada que viva en su propio espacio de nombres (por ejemplo, BooleanParser).
El objetivo de una clase es agrupar objetos similares u objetos con un comportamiento similar. 1
y 2
son muy similares, por lo tanto, tiene mucho sentido que estén en la misma clase. true
y false
sin embargo, no son similares. De hecho, su punto es que son exactamente lo opuesto entre sí y tienen un comportamiento opuesto. Por lo tanto, no pertenecen a la misma clase.
¿Puedes dar un ejemplo de qué tipo de comportamiento común implementarías en una clase Boolean
? No puedo pensar en nada.
Miremos el comportamiento que tienen TrueClass
y FalseClass
: allí hay exactamente cuatro métodos. No más. Y en cada caso, los dos métodos hacen exactamente lo contrario . ¿Cómo y por qué lo pondrías en una sola clase?
Así es cómo implementa todos esos métodos:
class TrueClass
def &(other)
other
end
def |(_)
self
end
def ^(other)
!other
end
def to_s
''true''
end
end
Y ahora al revés:
class FalseClass
def &(_)
self
end
def |(other)
other
end
def ^(other)
other
end
def to_s
''false''
end
end
De acuerdo, en Ruby, hay una gran cantidad de "magia" detrás de escena que TrueClass
y FalseClass
no FalseClass
sino que están FalseClass
en el intérprete. Cosas como if
, &&
, ||
y !
. Sin embargo, en Smalltalk, del cual Ruby pidió prestado mucho, incluido el concepto de FalseClass
y TrueClass
, todos estos se implementaron también como métodos, y usted puede hacer lo mismo en Ruby:
class TrueClass
def if
yield
end
def ifelse(then_branch=->{}, _=nil)
then_branch.()
end
def unless
end
def unlesselse(_=nil, else_branch=->{})
ifelse(else_branch, _)
end
def and
yield
end
def or
self
end
def not
false
end
end
Y otra vez al revés:
class FalseClass
def if
end
def ifelse(_=nil, else_branch=->{})
else_branch.()
end
def unless
yield
end
def unlesselse(unless_branch=->{}, _=nil)
ifelse(_, unless_branch)
end
def and
self
end
def or
yield
end
def not
true
end
end
Hace un par de años, escribí lo anterior solo por diversión e incluso lo publiqué . Aparte del hecho de que la sintaxis se ve diferente porque Ruby utiliza operadores especiales mientras yo uso solo métodos, se comporta exactamente como los operadores integrados de Ruby. De hecho, en realidad tomé el equipo de pruebas de conformidad RubySpec y lo transferí a mi sintaxis y se transfiere .
En Ruby nil y falso son falsos y todo lo demás es verdadero. Por lo tanto, no hay necesidad de una clase booleana específica.
Puedes probarlo :
if 5
puts "5 is true"
end
5 evalúa a verdadero
if nil
puts "nil is true"
else
puts "nil is false"
end
Se imprimirá "nil is false"
La razón principal es simplemente el hecho de que es mucho más rápido y sencillo implementar expresiones booleanas que en la actualidad con una clase booleana que implicaría una conversión.
Como le dijo Mongus Pong, cuando escribe "si", le pide al intérprete que lo evalúe y luego se bifurca. Si tuviera una clase booleana, tendría que convertir la evaluación de la cosa en una booleana antes de la bifurcación (un paso más).
Recuerde que tal conversión booleana -> estaría disponible como método Ruby en la clase booleana. Este método podría cambiarse dinámicamente como cualquier otro método de Ruby, permitiendo al desarrollador desordenar las cosas por completo (lo cual no es tan grave) pero, claramente, esto no le permitiría al intérprete optimizar las pruebas como debería.
¿Te das cuenta de que reemplazaría unas pocas instrucciones de funcionamiento de la CPU por una llamada a método completo, que es costosa en Ruby (recuerda el procesamiento del método "enviar") ...
Parece que el propio Matz respondió esta pregunta en un mensaje de lista de correo en 2004.
Versión resumida de su respuesta: "en este momento funciona bien, agregar un booleano no da ninguna ventaja".
Personalmente, no estoy de acuerdo con eso; el "análisis sintáctico de cadenas" antes mencionado es un ejemplo. Otra es que cuando se aplica un tratamiento diferente a una variable según su tipo (es decir, un analizador yml) tener una clase "booleana" es útil, se elimina uno "si". También parece más correcto, pero esa es una opinión personal.
verdadero y falso podría ser administrado por una clase booleana que contenía varios valores, pero luego el objeto de clase debería tener valores internos y, por lo tanto, debe ser referenciado con cada uso.
En cambio, Ruby trata true y false como valores largos (0 y 1), cada uno de los cuales corresponde a un tipo de clase de objeto (FalseClass y TrueClass). Al usar dos clases en lugar de una única clase booleana, cada clase no requiere ningún valor y, por lo tanto, se puede distinguir simplemente por su identificador de clase (0 o 1). Creo que esto se traduce en importantes ventajas de velocidad internas para el motor Ruby, porque internamente Ruby puede tratar TrueClass y FalseClass como valores largos que requieren cero traducción de su valor de ID, mientras que un objeto booleano debería ser desreferenciado antes de poder ser evaluado .