2012-07-11 41 views
4

我可以想象,對此的整體回答將推動我進入Functional Reactive Programming,但是...忍受着我一段時間。是否可以在GTK和Haskell中爲State monad中的數據創建TreeModel?

我也沒有這個問題的示例代碼。我用我的一些代碼在主題附近徘徊,但我一直堅守在它的IO monad中。

想象一下,我有一個應用程序,在這個應用程序中,我將建模一些複雜的狀態並將其放入整個應用程序狀態monad中。我這樣做是因爲我想要在覈心應用程序和特定用戶界面之間有一定程度的分離。

data S = S DataStore EventStream Sockets 
type AppState m = StateT S m 

(假設數據存儲,EventStream和套接字是所有的數據類型基本上做什麼,他們聽起來像:))

現在,說我想創建GTK(表中的TreeView,但沒有子節點)只能查看EventStream。我已經學會了這樣做,通過說listStoreNew event_stream >>= treeViewNewWithModel(見http://markus.alyra.org/?p=1023在那裏我非常廣泛地談論這個設置的機制)。

但是,現在我有一個可變副本我的AppState monad中的數據。當應用程序關閉並執行一些將新數據附加到EventStream的操作時,該視圖中不會顯示該數據。我能想到的唯一方法就是在視圖中顯示除了對monad所做的更改外,還會發送一條消息,如listStoreInsert my_new_event。這是可行的,但開始感覺笨拙。

更糟糕的是,這個神話般的樹視圖是一個管理視圖!這是可編輯!管理員說:「哦,那個事件有一些無效的數據,我想改變它!」。現在,我無法更改上面創建的ListStore中的數據。我可以創建回調,使更新沒有問題。但我無法想象如何將更新導入Global AppState Monad。

而那些最後幾個字顯示了問題的核心。如果我有一個全局的AppState Monad,那麼更新該monad的任何內容都必須在一行執行中,並且要查看monad的所有內容。 TreeView打破了這一點。當單元格在TreeView monad中編輯時,編輯處理程序完全在IO monad中運行,並且預計不會返回任何內容。最終數據類型爲IO()。即使我有一些漂亮的方式從我的AppState中打開數據,然後執行編輯處理程序,然後將數據重新包裝到AppState中,應用程序的其他分支無法看到它。

即使我能想出如何創建自己的完全自定義的ModelView實例,該實例向我的AppState提供只讀視圖,但我無法想象如何使狀態更新可用於其他應用程序。

所以......

它甚至有可能以這種方式來建模GTK /哈斯克爾應用?或者,我是否走上了瘋狂之路?

回答

1

您無法使用正常狀態monad可靠地共享狀態。如果(做作的例子)你的用戶通過GUI編輯模型,並且你從別的地方同時獲得新的條目?在這種情況下,你不可能使用一些純粹的monad堆棧將更改序列化到狀態monad。

你可以做的是使用某種使用可變引用的同步系統(以MVar爲例);您將實際應用程序狀態存儲在MVar中,只要發生了可能會讀取或更改狀態的事件,就可以訪問MVar。下面是一些僞代碼,顯示我的意思:

-- This is the MVar that stores your application state 
appStateMVar :: MVar S 
appStateMVar = unsafePerformIO $ newMVar initialAppState 
{-# NOINLINE appStateMVar #-} 
-- It could also be passed as a parameter to the functions below, so that when 
-- you define the callbacks, you create a closure over the MVar that you use. 
-- (i.e.: 
-- > appStateMVar <- newMVar initialAppState 
-- > createListViewWithCallback $ whenUserAddedSomethingViaTheGUI appStateMVar 
--) 
-- That way, you don't have to have the MVar in global scope and can avoid the 
-- use of `unsafePerformIO` to initialize it, etc. 

main :: IO() 
main = do 
    createListViewWithCallback whenUserAddedSomethingViaTheGUI 
    createSocketsAndListenUsingCallback whenChangesArriveOverTheNetwork 
    runSomeKindOfMainLoop 

-- This would be called on any thread by the GUI when the user added something in 
-- the view (For example) 
whenUserAddedSomethingViaTheGUI :: AddedThing -> IO() 
whenUserAddedSomethingViaTheGUI theThingThatWasAdded = 
    takeMVar appStateMVar >>= 
    execStateT (addToTheState theThingThatWasAdded) >>= 
    putMVar appStateMVar 

-- This would be called by the network when something changed there 
whenChangesArriveOverTheNetwork :: ArrivedChanges -> IO() 
whenChangesArriveOverTheNetwork theChangesThatArrived = 
    takeMVar appStateMVar >>= 
    execStateT (handleChanges theChangesThatArrived) >>= 
    putMVar appStateMVar 

然後,您可以使用純AppState單子寫addToTheStatehandleChanges,就像以前那樣。

當然,如果您決定使用FRP,您可以通過讓您的應用程序狀態成爲隨時間變化的純信號來避免這種非常必要的狀態佈線。據我所知,reactive-banana已經完成了一些工作,可以將雙向GUI編輯器/視圖集成到FRP事件網絡中。

+0

我認爲你的例子是非常現實的(即沒有人爲的)。我完全沒有想過使用MVar。這可能是一個相當有趣的解決方案。 – 2012-07-11 15:10:23

+0

我正在用手機寫這一切,所以請原諒這些混亂的解釋。之前我輸入的一個例子更具人爲性,涉及回調等不同的線程,但我意識到即使在具有不透明事件處理程序的單個線程上使用事件輪詢時也需要解決相同的問題。 – dflemstr 2012-07-11 15:55:35

+0

週末我開始玩這個解決方案。我喜歡。不禁感慨我插入了一個額外的間接層,但我希望能夠解決這個問題,因爲我在Haskell上做得更好。 – 2012-07-23 21:18:34