2016-10-14 5 views
7

私はF#を使用していますが、多くの場合、これらの言語の間にいくつかの類似点がある点を除いて、Scalaの多くは分かりません。しかし、ScalaでAkka Streamsの実装を見ている間、F#では不可能なように〜>という演算子を使用していました(残念ながら)。私は単項演算子の初めにF#でしか使用できないシンボル "〜"については言及していませんが、これは重要ではありません。さまざまなグラフ要素は、異なるタイプ(ソース、フロー、シンク)を持っているのでScalaではなぜこのような演算子の定義が可能ですか?

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

、それはそれら全体で仕事とF#で、単一の演算子を定義することはできません:何私を感動することは、このようにグラフを定義する可能性があります。 Scalaがなぜメソッド関数のオーバーロード(そしてF#はサポートしていない)をサポートしているのか?

更新。 Fydor Soikinは、F#でオーバーロードするいくつかの方法を示しました。これは、F#を使用するときに同様の構文を実現するために使用できます。私はそれがどのように見えるかをここでこれを試してみました:

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 

今、私たちは次のコードを記述することができます。

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 
+2

F#は完全にサポートメソッドのオーバーロードを行い:-)教訓を学びます。 –

答えて

10

必要に応じてクラスメンバーとしての演算子を定義することができ、 Scalaには少なくとも4つの異なる方法があります。

(1)メソッドオーバーロード。

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

(2)継承。

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

(3)オタクと類似の一般的な構造。

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

(必要な機能を提供するStreamable[Flow], Streamable[Sink], ...のインスタンス)。

(4)暗黙的な変換。

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

implicit def flowCanStream(f: Flow): Streamable = ???など)。

これらはそれぞれ独自の長所と短所を持ち、さまざまな図書館で多用されていますが、驚きを生成するのが容易すぎるため、最後は多少賛成できません。しかし、あなたが説明した振舞いを持つためには、これらのいずれかが機能します。

実際には、Akka Streamsでは、実際には私が知ることができるものから1-3の混合物です。あなたが実際に静的に解決型制約といくつかの巧妙な構文策略の助けを借りて、最初の引数でオーバーロードするトップレベルの演算子を達成することができ、その後

type T = 
    static member M (a: int) = a 
    static member M (a: string) = a 

let x = T.M 5 
let y = T.M "5" 

:すべての

+0

大きな説明ありがとうございます。 –

7

を実際に

type Base = 
    class 
    end 

type D1 = 
    class 
    inherit Base 
    static member (=>) (a: D1, b: D2): D2 = failwith "" 
    end 

and D2 = 
    class 
    inherit Base 
    static member (=>) (a: D2, b: D3): D3 = failwith "" 
    end 

and D3 = 
    class 
    inherit Base 
    static member (=>) (a: D3, b: string): string = failwith "" 
    end 

let a: D1 = failwith "" 
let b: D2 = failwith "" 
let c: D3 = failwith "" 

a => b => c => "123" 
+0

ありがとうございます。 –

6

まず、F#が完全にサポートメソッドのオーバーロードを行います。

type U = U with 
    static member inline ($) (U, a: int) = fun (b: string) -> a + b.Length 
    static member inline ($) (U, a: System.DateTime) = fun (b: int) -> string (int a.Ticks + b) 
    static member inline ($) (U, a: string) = fun (b: int) -> a.Length + b 

let inline (=>) (a: ^a) (b: ^b) = (U $ a) b 

let a = 5 => "55" // = 7 
let b = System.DateTime.MinValue => 55 // = "55" 
let c = "55" => 7 // = "9" 
let d = 5 => "55" => "66" => "77" // = 11 

そして、あなたは本当にだけでなく2番目の引数でオーバーロードしたい場合は、最後に、あなたはあまりにも、オーバーロードされたインスタンスメソッドからの助け入隊することによって行うことができます。

type I(a: int) = 
    member this.ap(b: string) = a + b.Length 
    member this.ap(b: int) = string(a + b) 

type S(a: string) = 
    member this.ap(b: int) = b + a.Length 
    member this.ap(b: string) = b.Length + a.Length 

type W = W with 
    static member inline ($) (W, a: int) = I a 
    static member inline ($) (W, a: string) = S a 

let inline ap (a: ^a) (b: ^b) = (^a : (member ap: ^b -> ^c) (a, b)) 
let inline (==>) (a: ^a) (b: ^b) = ap (W $ a) b 

let aa = 5 ==> "55" // = 7 
let bb = "55" ==> 5 // = 7 
let cc = 5 ==> "55" ==> 7 ==> "abc" ==> 9 // = "14" 

これらのすべての欠点は、すべてがコンパイル時に発生することです(すべての場所でinlineを参照してください)。 True型のクラスは間違いなく優れていますが、静的型の制約とオーバーロードだけで多くのことを行うことができます。

そしてもちろん、あなただけのようにもF#で、古き良き継承を行うことができます。

type Base() = class end 
type A() = inherit Base() 
type B() = inherit Base() 

let (===>) (a: #Base) (b: #Base) = Base() 

let g = A() ===> B() ===> A() 

しかし...継承?本当に?

それがトラブルまれ価値がある、と述べました。実際には、普通の関数を使って最終目的を達成することができます。また、オプションのオープン可能なカスタム演算子を散りばめただけでも便利です。過負荷のオペレータは、最初は光っているクールなおもちゃのように見えるかもしれませんが、過度に使い過ぎるのは危険です。 C++を覚えて、

+1

もちろん、メソッドのオーバーロードについては当然です。私が意味することは、関数のオーバーロードであり、メソッドのオーバーロードとして間違って参照されていました。アッカのため (スカラ)に似て定義するどうやら不能オペレーターF#が非常に少ない自由がAkka.NETクラスでF#で何ができるかにありますので、アッカは、C#1に.NETに移植されたという事実から来ているストリーム。それが最初の場所でF#への港だったら、もっと機会があると思います。 –

+0

上記の例で示したように、関数のオーバーロードも(ある程度まで)行うことができます。 –

+0

はい、もう一度やります。型定義によって可能です。 –

関連する問題