2011-12-21 46 views
17

我正在使用reactive-banana程序工作,我想知道如何構建我的類型與基本的玻璃鋼基本構件。例如,下面是我的真實程序的一個簡化示例:假設我的系統主要由小部件組成 - 在我的程序中,隨時間變化的文本片段。在哪裏申請玻璃鋼行爲(和其他類型)

我能有

newtype Widget = Widget { widgetText :: Behavior String } 

但我也可以有

newtype Widget = Widget { widgetText :: String } 

,並使用Behavior Widget時,我想談談隨時間變化的行爲。這似乎使事情「更簡單」,並且意味着我可以更直接地使用Behavior操作,而不必解包和重新打包Widgets來執行操作。

另一方面,前者似乎避免了實際定義小部件的代碼中的重複,因爲幾乎所有小部件隨着時間的推移而變化,並且我發現自己定義了一些與Behavior不同的部分,因爲它讓我以更一致的方式將他們與其他人結合起來。

作爲另一個例子,在兩種表示方式下,有一個Monoid實例(我希望在我的程序中有一個實例)是合理的,但後者的實現看起來更自然(因爲它只是一個微不足道的提升將monoid列入新類型)。

(我的實際程序使用Discrete而非Behavior,但我不認爲這是相關的。)

同樣,我應該使用Behavior (Coord,Coord)(Behavior Coord, Behavior Coord)代表一個2D點?在這種情況下,前者似乎是明顯的選擇;但是當它是代表遊戲中某個實體的五行記錄時,選擇似乎不那麼清晰。

在本質上,所有這些問題減少到:

當使用FRP,在哪一層,我應該使用Behavior類型?

(同樣的問題也適用於Event過,但在較小的程度。)

回答

5

dflemstr's advice

  1. 同意隔離「的東西,改變」儘可能多地。
  2. 將「同時變化的東西」組合成一個Behavior/Event

並希望爲這些經驗法則提供其他原因。

問題歸結爲以下幾點:你要代表隨時間變化的值的對(元組),問題是是否使用

一個。 (Behavior x, Behavior y) - 一對行爲

b。 Behavior (x,y) - 對一個行爲

理由喜歡一個比其他都是

  • A對B

    在推動驅動的實現中,行爲的改變將觸發重新計算依賴於它的所有行爲。

    現在,考慮一個行爲,其值僅取決於該對的第一個組件x。在變體a中,第二組件y的改變不會重新計算該行爲。但是在變體b中,行爲將被重新計算,即使其值並不依賴於第二個組件。換句話說,這是一個細粒度和粗粒度依賴關係的問題。

    這是忠告。當然了爭執,這是非常重視不是當雙方的行爲往往同時改變,這將產生建議2.

    當然,圖書館應提供一種方式來提供優良即使對於變體b也會產生依賴關係。從反應式香蕉版本0.4.3開始,這是不可能的,但現在不用擔心,我的推動式實現將在未來版本中成熟。

  • b超過

    看到反應式香蕉版本0.4.3不提供dynamic event switching但是,如果您將所有組件放在單一行爲中,則只能編寫某些程序。該標準示例將是一個程序,該程序具有變量個計數器,即TwoCounter.hs示例的擴展。您必須將其表示爲時間變化值列表

    counters :: Behavior [Int] 
    

    因爲沒有辦法跟蹤動態行爲集合。也就是說,下一個版本的響應式香蕉將包括動態事件切換。

    此外,您可以隨時變異一個轉換變種b沒有任何麻煩

    uncurry (liftA2 (,)) :: (Behavior a, Behavior b) -> Behavior (a,b) 
    
+1

那麼,在'Widget'的情況下,只有一個字段不是簡化,這是我的實際情況,所以沒有元組參與:)感謝您的幫助 - 但它應該是對未來非常有幫助!現在我會把'行爲'放在newtype裏面。我希望我能接受這兩個答案:) – ehird 2011-12-24 13:36:53

6

開發玻璃鋼的應用程序時,我使用的規則,分別是:

  1. 隔離「的事情改變」爲儘可能多。
  2. 將「同時改變的東西」組合成一個Behavior/Event

(1)的原因是,如果您使用的數據類型儘可能原始,則創建和組合抽象操作變得更容易。

原因是,像您所描述的那樣,Monoid等實例可以重用於原始類型。

請注意,您可以使用Lenses輕鬆修改數據類型的「內容」,就好像它們是原始值一樣,所以額外的「打包/解包」主要不是問題。 (請參閱this recent tutorial瞭解此特定鏡頭實施的介紹;有others

(2)的原因是它只是消除了不必要的開銷。如果兩件事情同時發生變化,那麼它們「具有相同的行爲」,所以它們應該被建模。

人機工程學/ TL;博士:你應該使用newtype Widget = Widget { widgetText :: Behavior String },因爲(1),並且你應該使用Behavior (Coord, Coord)因爲(2)(因爲這兩個座標通常同時改變)。

+0

我不認爲這裏的鏡頭幫助 - 使用'Monoid'例子,它是像'f = liftA2 mappend'這樣的東西就變成了'fab = Widget $ mappend(widgetText a)(widgetText b)'。無可否認,提升組合器可以緩解這種痛苦。但是,我不確定你想通過引用我的'Monoid'示例來說什麼 - 它意味着是String形式的參數,而不是'Behavior String'形式。 – ehird 2011-12-21 22:34:47

+0

雖然你的規則聽起來很不錯,但我還得多思考一下。非常感謝發佈這個!我不會接受這個答案,因爲我想聽聽其他觀點和觀點,因爲這是一個很微妙的問題。 – ehird 2011-12-21 22:36:20

+0

在你的'Widget'示例中,它只包含'widgetText',它使原始提升變得微不足道。如果你在'Widget'中有更多的值,原始提升變得比通過鏡頭提升'Behavior'並以這種方式執行操作複雜得多。 – dflemstr 2011-12-21 22:42:12