RE: [Haskell-cafe] Yet another top-level state proposal
At the risk of becoming repetitious, let's keep refining the Wiki to give these competing proposals in their most up-to-date form. I'm not arguing against email -- it's an excellent medium for discussion -- but having the outcomes recorded makes them accessible to a much wider audience who have not followed the detailed discussion. Simon | -Original Message- | From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED] On Behalf Of Judah | Jacobson | Sent: 28 May 2007 19:50 | To: Adrian Hey | Cc: haskell-cafe@haskell.org | Subject: Re: [Haskell-cafe] Yet another top-level state proposal | | On 5/26/07, Adrian Hey [EMAIL PROTECTED] wrote: | | Judah Jacobson wrote: | In contrast to recent proposals, this one requires no extra syntax or | use of unsafe functions by the programmer. | | I'm not aware of any proposals (recent or ancient:-) that require the | use of unsafe functions by the programmer (well apart from the | unsafePerformIO hack itself). | | I was referring to the proposal to make that hack somewhat safer by | adding a NO_INLINE_OR_CSE pragma. | | Also adding extra syntax is no problem | these days. It's trivially simple (and indeed desirable in this case | IMHO). It's the underlying compiler magic requires significant extra | work I think. | | Reading last week's conversation on the topic, I got the impression | that the debate is still ongoing with respect to that point. Although | this proposal is a little less aesthetic than those for mdo or ACIO, I | think the fact that it touches so few parts of the language might make | some people more comfortable with it. In particular, an | implementation only needs to: | | - add the OnceInit/OnceIO class declarations (trivial) | - add the OnceIO deriving clause logic (in GHC, this would be | isolated to one module) | - add a NO_CSE pragma at the Core syntax level. (already suggested for | other conservative proposals). | | But whether that's being *too* conservative is a matter of opinion, of course. | | | Under this proposal, we would write instead: | | newtype UniqueRef = UniqueRef (IORef Integer) | deriving OnceIO | | instance OnceInit UniqueRef where | onceInit = liftM UniqueRef (newIORef 0) | | A purely aesthetic objection, but to me it looks quite obfuscated | compared to: | | uniqueRef :: IORef Integer | uniqueRef - ACIO.newIORef 0 | | But I guess perhaps what's going on here could be made clearer with | the right syntactic sugar :-) | | If you're going to use syntactic sugar anyway, I think that negates | the main appeal of this proposal. Instead, we could ignore deriving | clauses altogether, and add an optional keyword oneshot to type | declarations, e.g.: | | oneshot uniqueRef :: IO (IORef Integer) | uniqueRef = newIORef 0 | | Now that I mention it, that idea's not too bad either... | | Finally, the useage problem I mentioned. Having to create a distinct | type for each top level thing with identity (my terminology) | seems like it could cause difficulties (probably not insoluble | problems though). | | My feeling is that most programs would use few enough TWIs that having | to declare extra types would not be a big hastle. But I see you're | challenging that point below: | | If you look at the wiki page you'll see the device driver example I | put there. This has two device handles at the top level (both same | type), with a device driver API that takes either the device handle | itself or a device session handle (which will contain the corresponding | device handle) as parameters (so in principle it can be used with any | number of devices provided the corresponding device handles are | available). | | My question is, what would this example look like using the solution | you propose? I can think of at least two possibilities, both of which | seem quite awkward. But I'll leave it to you to think about this | with a bit more care than perhaps I have. It'd be nice to see the | solution on the Wiki too. | | | If you want several different devices, you could wrap them all in one | large type: | | data DeviceHandle = ... | createDeviceHandle :: BaseAddress - IO DeviceHandle | | data AllHandles = AllHandles {handle1, handle2 :: DeviceHandle} deriving OnceIO | | instance OnceInit AllHandles where | onceInit = liftM2 AllHandles | (createDeviceHandle baseAddress1) | (createDeviceHandle baseAddress2) | | device1, device2 :: IO DeviceHandle | device1 = liftM handle1 runOnce | device2 = liftM handle2 runOnce | | This proposal does seem to encourage consolidating TWIs into one part | of the program; from a design perspective, that may not be entirely a | bad thing. | | Best, | -Judah | ___ | Haskell-Cafe mailing list | Haskell-Cafe@haskell.org | http://www.haskell.org/mailman/listinfo
Re: [Haskell-cafe] Yet another top-level state proposal
At the risk of becoming repetitious, let's keep refining the Wiki to give these competing proposals in their most up-to-date form. I'm not arguing against email -- it's an excellent medium for discussion -- but having the outcomes recorded makes them accessible to a much wider audience who have not followed the detailed discussion. i would have preferred some discussion here first, but to keep it from getting lost, i've now added my own proposal (different thread, in case you're wondering;) to http://www.haskell.org/haskellwiki/Top_level_mutable_state as '5 Proposal 4: Shared on-demand IO actions (oneShots)'. claus ___ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
Re: [Haskell-cafe] Yet another top-level state proposal
On 5/26/07, Adrian Hey [EMAIL PROTECTED] wrote: Judah Jacobson wrote: In contrast to recent proposals, this one requires no extra syntax or use of unsafe functions by the programmer. I'm not aware of any proposals (recent or ancient:-) that require the use of unsafe functions by the programmer (well apart from the unsafePerformIO hack itself). I was referring to the proposal to make that hack somewhat safer by adding a NO_INLINE_OR_CSE pragma. Also adding extra syntax is no problem these days. It's trivially simple (and indeed desirable in this case IMHO). It's the underlying compiler magic requires significant extra work I think. Reading last week's conversation on the topic, I got the impression that the debate is still ongoing with respect to that point. Although this proposal is a little less aesthetic than those for mdo or ACIO, I think the fact that it touches so few parts of the language might make some people more comfortable with it. In particular, an implementation only needs to: - add the OnceInit/OnceIO class declarations (trivial) - add the OnceIO deriving clause logic (in GHC, this would be isolated to one module) - add a NO_CSE pragma at the Core syntax level. (already suggested for other conservative proposals). But whether that's being *too* conservative is a matter of opinion, of course. Under this proposal, we would write instead: newtype UniqueRef = UniqueRef (IORef Integer) deriving OnceIO instance OnceInit UniqueRef where onceInit = liftM UniqueRef (newIORef 0) A purely aesthetic objection, but to me it looks quite obfuscated compared to: uniqueRef :: IORef Integer uniqueRef - ACIO.newIORef 0 But I guess perhaps what's going on here could be made clearer with the right syntactic sugar :-) If you're going to use syntactic sugar anyway, I think that negates the main appeal of this proposal. Instead, we could ignore deriving clauses altogether, and add an optional keyword oneshot to type declarations, e.g.: oneshot uniqueRef :: IO (IORef Integer) uniqueRef = newIORef 0 Now that I mention it, that idea's not too bad either... Finally, the useage problem I mentioned. Having to create a distinct type for each top level thing with identity (my terminology) seems like it could cause difficulties (probably not insoluble problems though). My feeling is that most programs would use few enough TWIs that having to declare extra types would not be a big hastle. But I see you're challenging that point below: If you look at the wiki page you'll see the device driver example I put there. This has two device handles at the top level (both same type), with a device driver API that takes either the device handle itself or a device session handle (which will contain the corresponding device handle) as parameters (so in principle it can be used with any number of devices provided the corresponding device handles are available). My question is, what would this example look like using the solution you propose? I can think of at least two possibilities, both of which seem quite awkward. But I'll leave it to you to think about this with a bit more care than perhaps I have. It'd be nice to see the solution on the Wiki too. If you want several different devices, you could wrap them all in one large type: data DeviceHandle = ... createDeviceHandle :: BaseAddress - IO DeviceHandle data AllHandles = AllHandles {handle1, handle2 :: DeviceHandle} deriving OnceIO instance OnceInit AllHandles where onceInit = liftM2 AllHandles (createDeviceHandle baseAddress1) (createDeviceHandle baseAddress2) device1, device2 :: IO DeviceHandle device1 = liftM handle1 runOnce device2 = liftM handle2 runOnce This proposal does seem to encourage consolidating TWIs into one part of the program; from a design perspective, that may not be entirely a bad thing. Best, -Judah ___ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
Re: [Haskell-cafe] Yet another top-level state proposal
-BEGIN PGP SIGNED MESSAGE- Hash: SHA1 Judah Jacobson wrote: Hi all, Given the recent discussion about adding top-level mutable state to Haskell, I thought it might be a good time to throw my own proposal into the ring. If enough people think it's worth considering, I can add it to the wiki page. (http://www.haskell.org/haskellwiki/Top_level_mutable_state) In contrast to recent proposals, this one requires no extra syntax or use of unsafe functions by the programmer. Any nonstandard magic that might occur is kept within the compiler internals. Furthermore, top-level initializations are only executed when needed; merely importing a module does not cause any additional actions to be run at startup. The core idea, similar to that of type-based execution contexts on the above wiki page, is to associate each top-level action with its own type. I like the idea, similar to deriving(Typeable) in creating uniqueness. Hopefully it can be made obvious somehow to code-readers unfamiliar with it that the code is doing something unusual/controversial... Isaac (P.S. having read the Top_level_mutable_state page now, it is definitely worth reading) -BEGIN PGP SIGNATURE- Version: GnuPG v1.4.6 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iD8DBQFGWB+6HgcxvIWYTTURAlu0AKCR7eIm9NQjnpUt4PbYhaIJylWgPwCgnZul JB4jszUNnd8+2HO+cR9LW7U= =RxW5 -END PGP SIGNATURE- ___ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
Re: [Haskell-cafe] Yet another top-level state proposal
On 5/26/07, Isaac Dupree [EMAIL PROTECTED] wrote: (P.S. having read the Top_level_mutable_state page now, it is definitely worth reading) I'm a newbie in Haskell, but for me this seems to be the better proposal until now. It's definitely worth adding to the wiki, at least. -- Felipe. ___ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
Re: [Haskell-cafe] Yet another top-level state proposal
Hello, It's nice to see someone else is seriously thinking about this and I can't see anything obviously wrong with this on first inspection so by all means add it to the wiki. I have a few issues with it on aesthetic grounds though. There also seems to be a bit of problem with using this solution in practice (I'll mention this at the end). Judah Jacobson wrote: In contrast to recent proposals, this one requires no extra syntax or use of unsafe functions by the programmer. I'm not aware of any proposals (recent or ancient:-) that require the use of unsafe functions by the programmer (well apart from the unsafePerformIO hack itself). Also adding extra syntax is no problem these days. It's trivially simple (and indeed desirable in this case IMHO). It's the underlying compiler magic requires significant extra work I think. Under this proposal, we would write instead: newtype UniqueRef = UniqueRef (IORef Integer) deriving OnceIO instance OnceInit UniqueRef where onceInit = liftM UniqueRef (newIORef 0) A purely aesthetic objection, but to me it looks quite obfuscated compared to: uniqueRef :: IORef Integer uniqueRef - ACIO.newIORef 0 But I guess perhaps what's going on here could be made clearer with the right syntactic sugar :-) Finally, the useage problem I mentioned. Having to create a distinct type for each top level thing with identity (my terminology) seems like it could cause difficulties (probably not insoluble problems though). If you look at the wiki page you'll see the device driver example I put there. This has two device handles at the top level (both same type), with a device driver API that takes either the device handle itself or a device session handle (which will contain the corresponding device handle) as parameters (so in principle it can be used with any number of devices provided the corresponding device handles are available). My question is, what would this example look like using the solution you propose? I can think of at least two possibilities, both of which seem quite awkward. But I'll leave it to you to think about this with a bit more care than perhaps I have. It'd be nice to see the solution on the Wiki too. Regards -- Adrian Hey ___ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
[Haskell-cafe] Yet another top-level state proposal
Hi all, Given the recent discussion about adding top-level mutable state to Haskell, I thought it might be a good time to throw my own proposal into the ring. If enough people think it's worth considering, I can add it to the wiki page. (http://www.haskell.org/haskellwiki/Top_level_mutable_state) In contrast to recent proposals, this one requires no extra syntax or use of unsafe functions by the programmer. Any nonstandard magic that might occur is kept within the compiler internals. Furthermore, top-level initializations are only executed when needed; merely importing a module does not cause any additional actions to be run at startup. The core idea, similar to that of type-based execution contexts on the above wiki page, is to associate each top-level action with its own type. For example, the current way to declare a source for unique integers is: {-# NOINLINE uniqueRef #-} uniqueRef :: IORef Integer uniqueRef = unsafePerformIO $ newIORef 0 uniqueInt :: IO Integer uniqueInt = do n - readIORef uniqueRef writeIORef uniqueRef (n+1) return n Under this proposal, we would write instead: newtype UniqueRef = UniqueRef (IORef Integer) deriving OnceIO instance OnceInit UniqueRef where onceInit = liftM UniqueRef (newIORef 0) uniqueInt :: IO Integer uniqueInt = do UniqueRef uniqueRef - runOnceIO n - readIORef uniqueRef writeIORef uniqueRef (n+1) return n The above code uses two classes: class OnceInit a where onceInit :: IO a class OnceInit a = OnceIO a where runOnceIO :: IO a The OnceInit class lets the programmer to specify how a type is initialized; above, it just allocates a new IORef, but we could also read a configuration file or parse command-line arguments, for example. In contrast, instances of the OnceIO class are not written by the programmer; instead, they are generated automatically by a deriving OnceIO clause.Each type for which OnceIO is derived will have a special top-level action associated with it, which is accessed through the runOnceIO function. Its semantics are: - The first time that runOnceIO is called, it runs the corresponding onceInit action and caches and returns the result. - Every subsequent time that runOnceIO is called, it returns the cached result. This behavior is safe precisely because runOnceIO is an IO action. Even though one can't guarantee when in the program an initialization will occur, when the initialization does happen it will be sequenced among other IO actions. To illustrate this behavior, here are a couple sample implementations in plain Haskell. These do use unsafePerformIO, but in practice any such details would be hidden in the instance derived by the compiler (along with any related NOINLINE/NOCSE pragmas): instance Once UniqueRef where runOnceIO = return $! unsafePerformIO onceInit or (less efficient, but multithreaded safe): instance Once UniqueRef where runOnceIO = modifyMVar onceUniqueRef $ \mx - case mx of Just x - return (Just x, x) Nothing - do {x - onceInit; return (Just x, x)} onceUniqueRef = unsafePerformIO $ newMVar Nothing Finally, note that the deriving clause can easily check whether the type in question is monomorphic (as is necessary for type-safety), since it already has access to the type definition. Anyway, that's the gist of my proposal; I hope I've explained it well, but please let let me know if you have questions, suggestions or criticisms. Best, -Judah ___ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe