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
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.
The ::() operator in Scala is utilized to add an element to the beginning of the List.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With