logo
首页技术栈工具库讨论
streaming
streaming
This package contains two modules, Streaming and Streaming.Prelude. The principal module, Streaming.Prelude, exports an elementary streaming prelude focused on a simple "source" or "producer" type, namely Stream (Of a) m r. This is a sort of effectful version of ([a],r) in which successive elements of type a arise from some sort of monadic action before the succession ends with a value of type r. Everything in the library is organized to make programming with this type as simple as possible, by the simple expedient of making it as close to Prelude and Data.List as possible. Thus for example the trivial program sums the first three valid integers from user input. Similarly, upper-cases the first two lines from stdin as they arise, and sends them to stdout. And so on, with filtering, mapping, breaking, chunking, zipping, unzipping, replicating and so forth: we program with streams of Ints or Strings directly as if they constituted something like a list. That's because streams really do constitute something like a list, and the associated operations can mostly have the same names. (A few, like reverse, don't stream and thus disappear; others like unzip are here given properly streaming formulation for the first time.) And we everywhere oppose "extracting a pure list from IO", which is the origin of typical Haskell memory catastrophes. Basically any case where you are tempted to use mapM, replicateM, traverse or sequence with Haskell lists, you would do better to use something like Stream (Of a) m r. The type signatures are a little fancier, but the programs themselves are mostly the same. In fact, they are mostly simpler. Thus, consider the trivial demo program mentioned in this SO question The new user notices that this exhausts memory, and worries about the efficiency of Haskell IORefs. But of course it exhausts memory! Look what it says! The problem is immediately cured by writing which really does what the other program was meant to do, uses no more memory than hello-world, and is simpler anyway, since it doesn't involve the detour of "extracting a list from IO". Almost every use of list mapM, replicateM, traverse and sequence produces this problem on a smaller scale. People get used to it, as if it were characteristic of Haskell programs to use a lot of memory. But in truth "extracting a list or sequence from IO" is mostly just bad practice pure and simple. Of course, mapM, replicateM, traverse and sequence make sense for lists, under certain conditions! But unsafePerformIO also makes sense under certain conditions. The Streaming module exports the general type, Stream f m r, which can be used to stream successive distinct steps characterized by any functor f, though we are mostly interested in organizing computations of the form Stream (Of a) m r. The streaming-IO libraries have various devices for dealing with effectful variants of [a] or ([a],r) in which the emergence of successive elements somehow depends on IO. But it is only with the general type Stream f m r, or some equivalent, that one can envisage (for example) the connected streaming of their sorts of stream - as one makes lists of lists in the Haskell Prelude and Data.List. One needs some such type if we are to express properly streaming equivalents of e.g. to mention a few obviously desirable operations. (This is explained more elaborately in the readme below.) One could of course throw something like the present Stream type on top of a prior stream concept: this is how pipes and pipes-group (which are very much our model here) use FreeT. But once one grasps the iterable stream concept needed to express those functions then one will also see that, with it, one is already in possession of a complete elementary streaming library - since one possesses Stream ((,) a) m r or equivalently Stream (Of a) m r. This is the type of a 'generator' or 'producer' or 'source' or whatever you call an effectful stream of items. The present Streaming.Prelude is thus the simplest streaming library that can replicate anything like the API of the Prelude and Data.List. The emphasis of the library is on interoperation; for the rest its advantages are: extreme simplicity, re-use of intuitions the user has gathered from mastery of Prelude and Data.List, and a total and systematic rejection of type synonyms. The two conceptual pre-requisites are some comprehension of monad transformers and some familiarity with 'rank 2 types'. It is hoped that experimentation with this simple material, starting with the ghci examples in Streaming.Prelude, will give people who are new to these concepts some intuition about their importance. The most fundamental purpose of the library is to express elementary streaming ideas without reliance on a complex framework, but in a way that integrates transparently with the rest of Haskell, using ideas - e.g. rank 2 types, which are here implicit or explicit in most mapping - that the user can carry elsewhere, rather than chaining her understanding to the curiosities of a so-called streaming IO framework (as necessary as that is for certain purposes.) See the readme below for further explanation, including the examples linked there. Elementary usage can be divined from the ghci examples in Streaming.Prelude and perhaps from this rough beginning of a tutorial. Note also the streaming bytestring and streaming utils packages. Questions about usage can be put raised on StackOverflow with the tag [haskell-streaming], or as an issue on Github, or on the pipes list (the package understands itself as part of the pipes 'ecosystem'.) The simplest form of interoperation with pipes is accomplished with this isomorphism: Interoperation with io-streams is thus: With conduit one might use, e.g.: These conversions should never be more expensive than a single >-> or =$=. The simplest interoperation with regular Haskell lists is provided by, say The latter of course accumulates the whole list in memory, and is mostly what we are trying to avoid. Every use of Prelude.mapM f should be reconceived as using the composition Streaming.toList_ . Streaming.mapM f . Streaming.each with a view to considering whether the accumulation required by Streaming.toList_ is really necessary. Here are the results of some microbenchmarks based on the benchmarks included in the machines package:
Cascade
Cascade
[Index] For package maintainers and hackage trustees <b>Cascade</b>s are collections of composable functions (e.g. a -> b, b -> c, ... , y -> z) where the intermediate types are stored in a type level list (e.g. Cascade [a,b,c,...,y,z]). For example, consider these Cascades: We can convert a cascade into a function easily enough: But that's not very inspiring. The real question, as Christian Conkle put it, is "what does such a collection give you over function composition?" Because none of the type information has been lost, we can still extract each of the functions that went into the Cascade using simple pattern matching. This opens the door to replacing parts of a cascade, or indexing into the cascade with type-level naturals. It also lets us do something silly like mix and match two different cascades: More seriously, we can record the intermediate results of each Cascade using a Product type as output: Or I can resume the computation at some later point rather that the first function in the Cascade using a Sum type as input: Or we could do both: But what's nice is that this generalizes nicely to categorical composition, so we can do the same with any category, including the Kleisli and Cokleisli categories for Monads and Comonads, respectively: Flipping back to ghci: resume works on both comonads and monads: record works on comonads, but I've been having some issues getting it to work the way I want on monads (see Cascade.Product.hs for more). So, instead of continue to wrestle the type system, for now, I just implemented a debugger that uses Cascades, so in addition running a monadic Cascade you can debug it. Dropping into ghci: We can see the current state of the debugger: the full stack trace back up, step forward: (Note that when we step forward, the monadic computation reruns) We can also use a different input than the default at the current stage: And since the debuggers are normal immutable haskell values, we can use both d and d' without errors. Cascades are still very limited. They're linear, and of a set length. They don't let you hook into functions that call themselves recursively, or functions that have computation paths better represented by trees or lattices. But that doesn't mean those are necessarily impossible to model, either. For example, simple recursion is fairly easily captured with only a slight modification to Cascade
definitive-base
definitive-base
The Definitive framework is an attempt at harnessing the declarative nature of Haskell to provide a solid and simple base for writing real-world programs, as well as complex algorithms. This package contains the base modules of the framework, and provides only the most basic functionality, ranging from basic algebraic structures, such as monoids and rings, to functors, applicative functors, monads and transformers. Lenses are used heavily in all the framework's abstractions, replacing more traditional functions (WriterT and runWriterT are implemented in the same isomorphism writerT, for example). When used wisely, lenses can greatly improve clarity in both code and thought, so I tried to provide for them in the most ubiquitous way possible, defining them as soon as possible. Isomorphisms in particular are surprisingly useful in many instances, from converting between types to acting on a value's representation as if it were the value itself. Packages using the Definitive framework should be compiled with the RebindableSyntax flag and include the Definitive module, which exports the same interface as the Prelude, except for some extras. Here is a list of design differences between the standard Prelude and the Definitive framework : The +, -, negate, and * are now part of the Semigroup, Disjonctive, Negative, Semiring classes instead of Num (default instances are defined to reimplement the Prelude, making it easy to adjust your code for compatibility) The mapM, traverseM, liftM, and such functions have been hidden, since they only reimplement the traverse, map, and other simpler functions. Lenses are used whenever possible instead of more usual functions. You are encouraged to read the interface for the Algebra.Lens module, which contains everything you will need to be able to use lenses to their full potential (except maybe a good explanation).
set-monad
set-monad
The set-monad library exports the Set abstract data type and set-manipulating functions. These functions behave exactly as their namesakes from the Data.Set module of the containers library. In addition, the set-monad library extends Data.Set by providing Functor, Applicative, Alternative, Foldable, Monad, and MonadPlus instances for sets. In other words, you can use the set-monad library as a drop-in replacement for the Data.Set module of the containers library and, in addition, you will also get the aforementioned instances which are not available in the containers package. It is not possible to directly implement instances for the aforementioned standard Haskell type classes for the Set data type from the containers library. This is because the key operations map and union, are constrained with Ord as follows. The set-monad library provides the type class instances by wrapping the constrained Set type into a data type that has unconstrained constructors corresponding to monadic combinators. The data type constructors that represent monadic combinators are evaluated with a constrained run function. This elevates the need to use the constraints in the instance definitions (this is what prevents a direct definition). The wrapping and unwrapping happens internally in the library and does not affect its interface. For details, see the rather compact definitions of the run function and type class instances. The left identity and associativity monad laws play a crucial role in the definition of the run function. The rest of the code should be self explanatory. The technique is not new. This library was inspired by [1]. To my knowledge, the original, systematic presentation of the idea to represent monadic combinators as data is given in [2]. There is also a Haskell library that provides a generic infrastructure for the aforementioned wrapping and unwrapping [3]. The set-monad library is particularly useful for writing set-oriented code using the do and/or monad comprehension notations. For example, the following definitions now type check. As noted in [1], the implementation technique can be used for monadic libraries and EDSLs with restricted types (compiled EDSLs often restrict the types that they can handle). Haskell's standard monad type class can be used for restricted monad instances. There is no need to resort to GHC extensions that rebind the standard monadic combinators with the library or EDSL specific ones. [1] CSDL Blog: The home of applied functional programming at KU. Monad Reification in Haskell and the Sunroof Javascript compiler. http://www.ittc.ku.edu/csdlblog/?p=88 [2] Chuan-kai Lin. 2006. Programming monads operationally with Unimo. In Proceedings of the eleventh ACM SIGPLAN International Conference on Functional Programming (ICFP '06). ACM. [3] Heinrich Apfelmus. The operational package. http://hackage.haskell.org/package/operational