看了一段talk by Bret Victor的視頻之後,我的靈感來源於寫了一個與他在演講中展示的開發環境有點相似的快速入侵。如何在不導致Haskell重新編譯的情況下更改我的數據類型?
基本上這個想法是,有一個應用程序在一個窗口中運行,並且每當程序發生變化時在源文件中保存更改。
這對於小的更改非常適用,但我無法在不關閉應用程序和重新編譯的情況下更改代碼中的狀態類型。
如何解決表達式問題並讓我的狀態的數據類型能夠在不導致重新編譯的情況下更改 ?
P.S. 這是代碼。 我本來不想發佈,因爲它非常混亂,很快就被黑了,但人們想要它,所以他們可以得到它。
首先顯示和空閒模塊,(這是一個快速入門,所以我沒有弄清楚如何做他們作爲真正的模塊)。
Idle.hs
\state -> do
counter <- readIORef state
writeIORef state ((counter + 1)`mod`3)
postRedisplay Nothing
Display.hs
\state -> let
cube w = do
renderPrimitive Quads $ do
vertex $ Vertex3 w w w
vertex $ Vertex3 w w (-w)
vertex $ Vertex3 w (-w) (-w)
vertex $ Vertex3 w (-w) w
vertex $ Vertex3 w w w
vertex $ Vertex3 w w (-w)
vertex $ Vertex3 (-w) w (-w)
vertex $ Vertex3 (-w) w w
vertex $ Vertex3 w w w
vertex $ Vertex3 w (-w) w
vertex $ Vertex3 (-w) (-w) w
vertex $ Vertex3 (-w) w w
vertex $ Vertex3 (-w) w w
vertex $ Vertex3 (-w) w (-w)
vertex $ Vertex3 (-w) (-w) (-w)
vertex $ Vertex3 (-w) (-w) w
vertex $ Vertex3 w (-w) w
vertex $ Vertex3 w (-w) (-w)
vertex $ Vertex3 (-w) (-w) (-w)
vertex $ Vertex3 (-w) (-w) w
vertex $ Vertex3 w w (-w)
vertex $ Vertex3 w (-w) (-w)
vertex $ Vertex3 (-w) (-w) (-w)
vertex $ Vertex3 (-w) w (-w)
points :: Integer -> [(GLfloat,GLfloat,GLfloat)]
points n' = let n = fromIntegral n' in map (\k -> let t = 2*pi*k/n in (sin(t),cos(t),0.0)) [1..n]
in do
clear [ ColorBuffer ]
counter <- readIORef state
mapM_ (\(x,y,z) -> preservingMatrix $ do
color $ Color3 ((x+1.0)/2.0) ((y+1.0)/2.0) ((z+1.0)/2.0)
translate $ Vector3 x y z
cube (0.3::GLfloat)
) $ points (9 + counter)
flush
主要模塊
module Main where
import Control.Monad
import Data.Typeable as Typeable
import System.IO
import Data.IORef
import Graphics.Rendering.OpenGL
import Graphics.UI.GLUT
import Language.Haskell.Interpreter
main :: IO()
main = do
(_, _) <- getArgsAndInitialize
createWindow "Hello World"
action <- newIORef $ do
clear [ ColorBuffer ]
flush
let imports = ["Prelude", "Data.IORef", "Graphics.Rendering.OpenGL", "Graphics.UI.GLUT"]
let modules = ["State"]
runFile (undefined :: IORef Integer -> IO()) "Display.hs" imports $ \displayCode ->
runFile (undefined :: IORef Integer -> IO()) "Idle.hs" imports $ \idleCode -> do
state <- newIORef 12
displayCallback $= display displayCode state
idleCallback $= Just (idle displayCode idleCode state)
mainLoop
display displayCode state = do
f <- execute displayCode
f state
idle displayCode idleCode state = do
update displayCode
update idleCode
f <- execute idleCode
f state
instance Eq GhcError where
GhcError s == GhcError t = s == t
instance Eq InterpreterError where
UnknownError s == UnknownError t = s == t
WontCompile s == WontCompile t = s == t
NotAllowed s == NotAllowed t = s == t
GhcException s == GhcException t = s == t
data V a = V {
update :: IO(),
execute :: IO a
}
runFile :: Typeable a => a -> String -> [String] -> (V a -> IO()) -> IO()
runFile theType file imports f = do
currentError <- newIORef Nothing
currentAction <- newIORef Nothing
let v = V {
update = do
fileContents <- readFile file
result <- runInterpreter $ do
setImports imports
interpret fileContents theType
oldError <- readIORef currentError
case result of
Right newAction -> do
when (oldError /= Nothing) $ do
writeIORef currentError Nothing
putStrLn (file ++ " Ok!")
writeIORef currentAction (Just newAction)
Left newError -> do
when ((Just newError) /= oldError) $ do
writeIORef currentError (Just newError)
print newError
, execute = do
action <- readIORef currentAction
case action of
Nothing -> do
err <- readIORef currentError
return (error (show err))
Just act -> return act
}
update v
f v
+1對Bret Victor的演講採取行動。如果你可以在任何地方發佈你的代碼,那將會很棒。我認爲靜態類型檢查的語言不太適合這樣的環境。如果你堅持使用靜態類型,那麼運行時(至少是調試開發運行時)應該拋棄靜態類型並使用動態類型。我不確定像Haskell這樣的運行時存在。 – 2012-02-25 19:31:16
@ user990666你可以發佈鏈接到談話嗎? – 2012-02-25 20:10:14
@Matt Fenwick http://vimeo.com/36579366 – 2012-02-25 20:51:10