functional-programming - example - racket loops
Variante funcional de la funciĆ³n ''oneof'' en Racket (3)
He escrito la siguiente función para encontrar si una y solo una de las 5 variables es verdadera:
(define (oneof v w x y z)
(or (and v (not w) (not x) (not y) (not z))
(and w (not v) (not x) (not y) (not z))
(and x (not v) (not w) (not y) (not z))
(and y (not v) (not w) (not x) (not z))
(and z (not v) (not w) (not x) (not y)) ))
(xor toma solo 2 argumentos)
Sin embargo, es muy imperativo y no funcional. Además, quiero escribir una función (una de N) que será genérica en lugar de específica para 5 variables. ¿Cómo puede hacerse esto? Gracias.
Editar: Como se señala en los comentarios, el código es ''repetitivo'' y no ''imperativo'', aunque la necesidad de su mejora permanece.
Otra solución, más concisa, es la siguiente:
(define (oneof . vars)
(= 1 (count identity vars)))
Usted puede contar literalmente cuántos valores true
tiene en una lista de longitud arbitraria, si ese número es 1
entonces estamos bien (recuerde que en Scheme cualquier valor no false
se considera true
). También observe cómo crear un procedimiento con un número variable de argumentos, usando la notación de puntos:
(define (oneof . vars)
(= 1
(count (lambda (v) (not (false? v)))
vars)))
Por ejemplo:
(oneof #f #f #f #f #t)
=> #t
(oneof #f #f #f #f #f)
=> #f
(oneof #t #t #t #t #t)
=> #f
Su nota final es precisa: parece que xor
es la función correcta, pero esto solo requiere dos argumentos. Probablemente sería mejor si xor
tomara cualquier cantidad de argumentos, pero dado que no es así, podemos implementarlo nosotros mismos.
Quizás la manera más ingenua sería simplemente contar el número de valores de verdad y verificar si ese número es precisamente 1. Podemos hacer esto con count
o for/sum
, dependiendo de su preferencia:
; using count
(define (xor . args)
(= 1 (count identity args)))
; using for/sum
(define (xor . args)
(= 1 (for/sum ([x (in-list args)])
(if x 1 0))))
Ambos funcionan, pero no conservan una propiedad útil del xor
de Racket, que devuelve el elemento true truth al éxito en lugar de devolver siempre un booleano. Para hacer esto, podríamos usar un doblez, usando foldl
, foldr
o for/fold
. Sin embargo, dado que queremos idealmente salir tan pronto como sea posible, usar la opción #:final
de for/fold
es bastante conveniente:
(define (xor . args)
(for/fold ([found #f])
([arg (in-list args)])
#:final (and found arg)
(if (and found arg) #f
(or found arg))))
Sin embargo, esto en realidad todavía no es realmente óptimo . La versión de dos argumentos de xor
es una función, no una macro como andy or
, porque no puede ser floja en ninguno de sus argumentos. Sin embargo, un argumento xor
realidad puede serlo. Para agregar este comportamiento de cortocircuito, podemos escribir xor
como una macro:
(define-syntax xor
(syntax-rules ()
[(_) #f]
[(_ x) x]
[(_ x rest ...)
(let ([v x])
(if v
(and (nor rest ...) v)
(xor rest ...)))]))
En general, esto funciona igual que las versiones de función de xor
:
> (xor #f #f #f #f #f)
#f
> (xor #f #f 1 #f #f)
1
> (xor #f #f 1 2 #f)
#f
Sin embargo, como and
or
, a veces "cortocircuitos", no se evalúan las expresiones si sus resultados no son maternos:
> (xor #f #f #f #f (begin (displayln "hello!") #f))
hello!
#f
> (xor #f #f 1 #f (begin (displayln "hello!") #f))
hello!
1
> (xor #f #f 1 2 (begin (displayln "hello!") #f))
#f
(Tenga en cuenta que hello!
Nunca se imprime en el último ejemplo).
¿Es esta una buena idea, es una mala idea ...? Realmente no lo sé Parece poco probable que este comportamiento alguna vez sea súper útil y agrega mucha complejidad. También evita que xor
se use en orden superior, pero se puede evitar con syntax-id-rules
y expandirse a la versión del procedimiento cuando xor
se usa en una posición de expresión. Aún así, es potencialmente interesante, y hace que su comportamiento sea más consistente con andy or
, así que pensé que lo incluiría para completarlo.