Repository: hylang/hy

Issue:
Unary divide
My understanding of the design goals of Hy is to be homoiconic Python. I think Python libraries + Lisp macros = awesome. From reading the Hy Style guide, the design guidelines seem to be defer to Python3, to Clojure, to Common Lisp, in that order. I think that's a great plan. Clojure seems to be very well designed.

So where's the unary divide?

``` Lisp
;;; Clojure
user=> (/ 2)
1/2

;;; Common Lisp
* (/ 2)
1/2

;;; Hy
;; Guesses anyone? Sadly, Python doesn't have a ratio type and neither does Hy.
;; But at least you'd expect (/ 1 2) to equal (/ 2), right?
=> (/ 1 2)
0.5
;; Wrong.
=> (/ 2)
  File "<input>", line 1, column 1

  (/ 2)
  ^---^
HyTypeError: `/' needs at least 2 arguments, got 1.
```

Yeah, I was surprised too. Is this a bug? Or a design oversight?

Python doesn't have a unary divide, so I have to assume this was on purpose and plead my case. But you know, it doesn't have a ternary divide either, and yet this seems to work fine:

``` Lisp
=> (/ 6 3 2)
1.0
```

One might argue that this changes the semantics of divide. The first number should be the dividend, and the rest divisors. Now you're dividing an implicit 1. Well, yeah, you need a divisor, or it's not division. But you have to realize that unary minus already works the same way:

``` Lisp
=> (- 0 2)
-2
=> (- 2)
-2
```

You're subtracting from an implicit 0. Why break the symmetry?

We can't defer exactly to Python in all cases, or Hy would still be Python. There is sufficient reasonable doubt about designing it Python's way. We should defer to our fallback guides of Clojure and Common Lisp, which both agree on this point. Unary minus means additive inverse; unary divide means multiplicative inverse. I have yet to meet another lisp with Hy's problem. Even Scheme does it this way.

``` Scheme
;;; R5RS
> (/ 2)
1/2
```

It should be the same in Hy:

``` Lisp
;;; doesn't actually work in Hy yet.
=> (/ 2)
0.5
```

I could be wrong here, but it doesn't _seem_ like a particularly difficult fix. I could almost write a macro to insert the implicit 1 in case of one argument, but I don't think I can override the built-in symbols. 

This change also shouldn't break any existing Hy code.

Objections?

Actually Python DOES have a ratio type:

``` Python
# Python3
>>> from fractions import Fraction
>>> Fraction(16, -10)
Fraction(-8, 5)
```

Hy could definitely make Fraction prettier with ratio literals like all the other Lisps. It's something to think about, but I think it's a separate issue from the strangely lacking unary division. I must concede that integer division returning floats is more consistent with the way Python3 works.



Discussion:
@gilch let's break this into two - first is lack of `(/ 2)` support. I think that's not bad. Let's add it. 

Here's how it was added for plus - https://github.com/hylang/hy/blob/master/hy/compiler.py#L1725

Also let's double check on the shadow opers

Second: the fraction type. Interesting idea. Can we file a second bug on that and see what we can do there?

I'll submit another one for the ratio literals.

What I'm envisioning (here I'm going to use `\` for a fraction literal):

``` hy
(/ x) => (/ 1 x) ; reciprocal, like K
(\ x y) => (Fraction x y)
(\ y) => (Fraction 1 y)
```

Good gut, but I'm thinking that might be a bit confusing.

I'm mostly worried about how this interacts with code that expect a Long or
something.

Explicitly doing this won't be the end of the world (esp. with macros), but
some sort of contrib addon would be interesting.

On Fri, Jun 26, 2015 at 3:34 PM, Ryan Gonzalez notifications@github.com
wrote:

> What I'm envisioning (here I'm going to use \ for a fraction literal):
> 
> (/ x) => (/ 1 x) ; reciprocal, like K
> (\ x y) => (Fraction x y)
> (\ y) => (Fraction 1 y)
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/hylang/hy/issues/825#issuecomment-115849202.

## 

:wq

@paultag 

> I'm mostly worried about how this interacts with code that expect a Long or
> something.

?? I'm not quite sure why `(= (/ 2) 0.5)` would cause any more trouble with longs than Python 3's float division. Or are you referring to something else?


Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -1721,8 +1721,6 @@ def compile_compare_op_expression(self, expression):
                                  col_offset=e.start_column)
 
     @builds("%")
-    @builds("/")
-    @builds("//")
     @builds("**")
     @builds("<<")
     @builds(">>")
@@ -1764,11 +1762,14 @@ def compile_maths_expression(self, expression):
 
     @builds("+")
     @builds("*")
+    @builds("/")
+    @builds("//")
     def compile_maths_expression_mul(self, expression):
         if len(expression) > 2:
             return self.compile_maths_expression(expression)
         else:
-            id_op = {"+": HyInteger(0), "*": HyInteger(1)}
+            id_op = {"+": HyInteger(0), "*": HyInteger(1), "/": HyInteger(1),
+                     "//": HyInteger(1)}
 
             op = expression.pop(0)
             arg = expression.pop(0) if expression else id_op[op]
diff --git a/hy/lex/parser.py b/hy/lex/parser.py
--- a/hy/lex/parser.py
+++ b/hy/lex/parser.py
@@ -266,6 +266,14 @@ def t_identifier(p):
     except ValueError:
         pass
 
+    if '/' in obj:
+        try:
+            lhs, rhs = obj.split('/')
+            return HyExpression([HySymbol('fraction'), HyInteger(lhs),
+                                 HyInteger(rhs)])
+        except ValueError:
+            pass
+
     try:
         return HyFloat(obj)
     except ValueError:


Test patch:
diff --git a/tests/lex/test_lex.py b/tests/lex/test_lex.py
--- a/tests/lex/test_lex.py
+++ b/tests/lex/test_lex.py
@@ -108,6 +108,13 @@ def test_lex_integers():
     assert objs == [HyInteger(42)]
 
 
+def test_lex_fractions():
+    """ Make sure that fractions are valid expressions"""
+    objs = tokenize("1/2")
+    assert objs == [HyExpression([HySymbol("fraction"), HyInteger(1),
+                                  HyInteger(2)])]
+
+
 def test_lex_expression_float():
     """ Make sure expressions can produce floats """
     objs = tokenize("(foo 2.)")
diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy
--- a/tests/native_tests/language.hy
+++ b/tests/native_tests/language.hy
@@ -12,6 +12,11 @@
   (assert (isinstance sys.argv list)))
 
 
+(defn test-fractions []
+  "NATIVE: test fractions"
+  (assert (= 1/2 (fraction 1 2))))
+
+
 (defn test-lists []
   "NATIVE: test lists work right"
   (assert (= [1 2 3 4] (+ [1 2] [3 4]))))