Skip to content

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

Closed
@scabug

Description

@scabug

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions