Skip to content

Need left-associative operator that is a method of the right-hand side (rhs) operand #8980

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
scabug opened this issue Nov 13, 2014 · 3 comments

Comments

@scabug
Copy link

scabug commented Nov 13, 2014

I'm coding a nullable type that improves upon Option to be both unboxed and provide an intuitive Applicative syntax for function and methods. In other words, I can apply instances of the nullable type to any function or method that doesn't understand the nullable type, which eliminates boilerplate. And the syntax should look similar to the normal use of a method or function (see the {code}test{code} functions).

First attempt using a left-associate operator * that is a method of the left-hand side operand has the problem that I need to write the function and method invocations in right-to-left order as well use parenthesis to enforce right-associativity (see the object Test2).

class Test {
   def method = ""
   def method1(i:Int) = i
   def method2(i:Int,b:Boolean) = (i,b)
   def method2 = Nullable("")
}

final case class Nullable2[T](v:T = null) extends AnyVal {
   def *[B](b:Nullable2[T=>B]) = Nullable2(if (this.v != null && b.v != null) b.v(this.v) else Null[B])
}


object Test2 {
   def method = Nullable2((_:Test).method)
   def method1 = Nullable2((t:Test) => t.method1(_:Int))
   def method2 = Nullable2((t:Test) => (i:Int) => t.method2(i, _:Boolean))
   implicit def toNullable2[T](v:T) = Nullable2(v)
   val instance = Nullable2(new Test)
   def test = Nullable2(new Test)*method
   def test1 = Nullable2(3)*(instance*method1)
   def test2 = true*(Nullable2(3)*(instance*method2))
   def test3 = true*(Nullable2(3)*(instance*method2))
   def test4 = true*(Nullable2(3)*(Nullable2(new Test)*method2))
}

Next attempt using a right-associate operator *: that is a method of the right-hand side operand has the correct function and method invocations in left-to-right order but the problem that I need to use parenthesis to enforce left-associativity (see the {code}object Test3{code}).

final case class Nullable3[T](v:T = null) extends AnyVal {
   def *[B](b:Nullable3[T=>B]) = Nullable3(if (this.v != null && b.v != null) b.v(this.v) else Null[B])
   def *:[B](b:Nullable3[T=>B]) = Nullable3(if (this.v != null && b.v != null) b.v(this.v) else Null[B])
}


object Test3 {
   def method = Nullable3((_:Test).method)
   def method1 = Nullable3((t:Test) => t.method1(_:Int))
   def method2 = Nullable3((t:Test) => (i:Int) => t.method2(i, _:Boolean))
   implicit def toNullable3[T](v:T) = Nullable3(v)
   val instance = Nullable3(new Test)
   def test = instance*method
   def test1 = (instance*method1)*:Nullable3(3)
   def test2 = ((instance*method2)*:3)*:true
   def test3 = ((method2*:instance)*:3)*:true
   def test4 = ((method2*:(new Test))*:3)*:true
}

I found a work-around which has much cleaner {code}test{code} functions result, but it requires boilerplate and can’t handle a function or method of arbitrary number of input parameters. This work-around does have the advantage that inputting the literal {code}null:B{code} (versus {code}NullableB{code}) does not short-circuit the function, i.e. not prevent the function from being called, but this could be worked around by flagging with a special object to indicate {code}null:B{code}.

/*
[1] http://jnordenberg.blogspot.com/2013/03/a-more-efficient-option.html
*/
final case class Nullable[T](v:T = null) extends AnyVal {
   def *[B](b:NullableFunction1[T,B]) = b<this
   def *[B,C](b:NullableFunction2[T,B,C]) = b<this
   def *[B,C,D](b:NullableFunction3[T,B,C,D]) = b<this
   def > = this
}

trait NullableFunction[A,R] extends Any {
   def f:A=>R
   def arrow(a:A) = if (this.f != null) this.f(a) else Null[R]
   def arrow(a:Nullable[A]) = if (this.f != null && a.v != null) this.f(a.v) else Null[R]
}

final case class NullableFunction1[A,B](f:A=>B = null) extends AnyVal with NullableFunction[A,B] {
   def |(a:A) = Nullable(arrow(a))
   def |(a:Nullable[A]) = Nullable(arrow(a))
   def <(a:A) = this|a
   def <(a:Nullable[A]) = this|a
}

final case class NullableFunction2[A,B,C](f:A=>(B=>C) = null) extends AnyVal with NullableFunction[A,B=>C] {
   def |(a:A) = NullableFunction1(arrow(a))
   def |(a:Nullable[A]) = NullableFunction1(arrow(a))
   def <(a:A) = this|a
   def <(a:Nullable[A]) = this|a
}

final case class NullableFunction3[A,B,C,D](f:A=>(B=>(C=>D)) = null) extends AnyVal with NullableFunction[A,B=>(C=>D)] {
   def |(a:A) = NullableFunction2(arrow(a))
   def |(a:Nullable[A]) = NullableFunction2(arrow(a))
   def <(a:A) = this|a
   def <(a:Nullable[A]) = this|a
}

object Test {
   def method = NullableFunction1((_:Test).method)
   def method1 = NullableFunction2((t:Test) => t.method1(_:Int))
   def method2 = NullableFunction3((t:Test) => (i:Int) => t.method2(i, _:Boolean))
   val instance = Nullable(new Test)
   def test = instance*method
   def test1 = instance*method1<Nullable(3)>
   def test2 = instance*method2<3|true>
   def test3 = method2<instance|3|true>
   def test4 = method2<(new Test)|3|true>
}

Compare.

  1. Left-associative, method of lhs operand:
   def test = Nullable2(new Test)*method
   def test1 = Nullable2(3)*(instance*method1)
   def test2 = true*(Nullable2(3)*(instance*method2))
   def test3 = true*(Nullable2(3)*(instance*method2))
   def test4 = true*(Nullable2(3)*(Nullable2(new Test)*method2))
  1. Right-associative, method of rhs operand:
   def test = instance*method
   def test1 = (instance*method1)*:Nullable3(3)
   def test2 = ((instance*method2)*:3)*:true
   def test3 = ((method2*:instance)*:3)*:true
   def test4 = ((method2*:(new Test))*:3)*:true
  1. Suggested improvement left-associative, method of rhs operand (or my work-around):
   def test = instance*method
   def test1 = instance*method1<Nullable(3)>
   def test2 = instance*method2<3|true>
   def test3 = method2<instance|3|true>
   def test4 = method2<(new Test)|3|true>

Note I am unable to implement the following because the first instance of →must have a higher precedence than | and the latter instance must of the same or lower precedence.

   def test2 = instancemethod2<3→true>

It would be much more visually intuitive if I could overload the dot(.), comma(,), and parenthesis as operators as follows.

   def test2 = instance.method2(3,true)

EDIT: I see there is the capability to pass (assume optionally with call-by-name) a parenthesized list of arguments to an overloaded operator[2]. I didn't yet try to implement this and it might increase the boilerplate of the library implementation. So in theory I could achieve as follows.

   def test2 = instance*method2<(3,true)>

Or at least the ability to overload some visually similar Unicode symbols[1] with the same precedence such as follows.

   def test2 = instance·method2⸨3true

I don't know Scala macros well enough yet to determine if they can help me (i.e. do they have enough type information), yet in any case much better to improve this so the implementation is more comprehensible to a wider audience.

[1] http://www.scala-lang.org/old/node/4723
[2] http://www.scala-lang.org/docu/files/ScalaReference.pdf#subsection.6.12.3

@scabug
Copy link
Author

scabug commented Nov 13, 2014

Imported From: https://issues.scala-lang.org/browse/SI-8980?orig=1
Reporter: Shelby Moore III (shelby)
Affected Versions: 2.11.2

@scabug
Copy link
Author

scabug commented Nov 13, 2014

@soc said:
I think there is pretty much no chance for any changes in this syntax department, especially when they make things more complicated.

Also, creating a ticket here is not the right approach:
Create a topic in scala-debate or scala-internals, and if there is consensus that this should be done (very unlikely, but that's just imho), come back to this ticket, re-open it and assign it to the person who volunteered implementing it.

@scabug scabug closed this as completed Nov 13, 2014
@scabug
Copy link
Author

scabug commented Nov 13, 2014

Shelby Moore III (shelby) said (edited on Nov 13, 2014 7:21:38 PM UTC):
I understand the point about discussing it at scala-debate or scala-internals to try to gain consensus. One advantage of writing it down here first versus posting to a Google group, is the prettier code blocks and ability to edit my post until I get it perfected.

However, I don't understand your assertion that my suggested improvement makes anything more complicated. Note the amount of spaghetti code needed to implement case #3 without the suggested improvement. Also note that it appears it is impossible to implement an Applicative without the improvement, at least not without violating Scala subtyping as Scalaz does.

This is serious deficiency. Almost enough so that I was tempted to classify it as a bug, not just an improvement. However, one may be able to argue successfully that subtyping isn't congruent with Applicative, which I suspect might be Tony Morris's position.

Debating it at scala-* is not within my available free time at the moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant