Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why such operator definition is possible in Scala?

I use F# and don't know much of Scala, except that there are often some similarities between these languages. But while looking at Akka Streams implementation in Scala, I noticed the use of operator ~> in such a way that's not possible in F# (unfortunately). I am not talking about symbol "~" that can only be used in F# in the beginning of unary operators, this is not important. What impressed me is possibility to define graphs like this:

in ~> f1 ~> bcast ~> f2 ~> merge ~> f3 ~> out
            bcast ~> f4 ~> merge

Since various graph elements have different types (Source, Flow, Sink), it's not possible to define a single operator in F# that would work across them. But I wonder why this is possible in Scala - it this because Scala supports method function overloading (and F# doesn't)?

UPDATE. Fydor Soikin showed several ways of overloading in F# that could be used to achieve similar syntax when using F#. I tried this and here how it might look:

type StreamSource<'a,'b,'c,'d>(source: Source<'a,'b>) = 
    member this.connect(flow : Flow<'a,'c,'d>) = source.Via(flow)
    member this.connect(sink: Sink<'a, Task>) = source.To(sink)

type StreamFlow<'a,'b,'c>(flow : Flow<'a,'b,'c>) = 
    member this.connect(sink: Sink<'b, Task>) = flow.To(sink)

type StreamOp = StreamOp with
    static member inline ($) (StreamOp, source: Source<'a,'b>) = StreamSource source
    static member inline ($) (StreamOp, flow : Flow<'a,'b,'c>) = StreamFlow flow

let inline connect (a: ^a) (b: ^b) = (^a : (member connect: ^b -> ^c) (a, b)) 
let inline (>~>) (a: ^a) (b: ^b) = connect (StreamOp $ a) b

Now we can write the following code:

let nums = seq { 11..13 }
let source = nums |> Source.From
let sink = Sink.ForEach(fun x -> printfn "%d" x)
let flow = Flow.FromFunction(fun x -> x * 2)
let runnable = source >~> flow >~> sink
like image 871
Vagif Abilov Avatar asked Oct 14 '16 19:10

Vagif Abilov


People also ask

How do operators work in Scala?

In Scala, all operators are methods. Operators themselves are just syntactic sugar or a shorthand to call methods. Both arithmetic addition (+) and String. charAt() are examples of infix operators.

What is :: operator in Scala?

The ::() operator in Scala is utilized to add an element to the beginning of the List.

What is operator overloading in Scala?

Operator Overloading in Scala Scala supports operator overloading, which means that the meaning of operators (such as * and + ) may be defined for arbitrary types. Example: a complex number class: class Complex(val real : Double, val imag : Double) { def +(other : Complex) = new Complex( real + other.


1 Answers

Actually, Scala has at least four different ways to make it work.

(1) Method overloading.

def ~>(f: Flow) = ???
def ~>(s: Sink) = ???

(2) Inheritance.

trait Streamable { 
  def ~>(s: Streamable) = ???
}
class Flow extends Streamable { ... }
class Sink extends Streamable { ... }

(3) Typeclasses and similar generic constructs.

def ~>[A: Streamable](a: A) = ???

(with Streamable[Flow], Streamable[Sink], ... instances that provide the needed functionality).

(4) Implicit conversions.

def ~>(s: Streamable) = ???

(with implicit def flowCanStream(f: Flow): Streamable = ???, etc.).

Each of these have their own strengths and weaknesses, and all are used heavily in various libraries, though the last has fallen somewhat out of favor due to it being too easy to generate surprises. But to have the behavior you have described, any of these would work.

In practice, in Akka Streams, it's actually a mixture of 1-3 from what I can tell.

like image 89
Rex Kerr Avatar answered Sep 28 '22 01:09

Rex Kerr