Peteris Krumins has a new book, Perl One-Liners Explained. His new book is in the same style as his previous books on awk and sed, reviewed here and here.
All the books in this series are organized by task. For each task, there is a one-line solution followed by detailed commentary. The explanations frequently offer alternate solutions with varying degrees of concision and clarity. Sections are seldom more than one page long, so the books are easy to read a little at a time.
Programmers who have written a lot of Perl may still learn a few things from Krumins. In particular, those who have primarily written Perl in script files may not be familiar with some of the tricks for writing succinct Perl on the command line.
Other Perl posts:
All languages equally complex?
Periodic table of Perl operators
Three-hour-a-week language
[I was asked about Cubes “Where do you think you might take the game play next?” and it turned into this.]
My original motivation for creating Cubes was a combination of the “blocks out of blocks” idea — which itself came from immersion in the graphics of Minecraft — and also dissatisfaction with certain bugs, limitations, and design choices in Minecraft. As a result, I’m not just building a voxel game; I’m building a game that shares what I like about Minecraft.
(What I like about Minecraft, broadly, is survival and engineering — I like building structures and machines to make my virtual life easier.)
Now, creating a Minecraft clone would be lame, rude, closer to using someone else’s intellectual property, and just plain unoriginal. But I don’t have experience with what little exists of a genre of voxel building games to synthesize my own thing, and I myself am looking for something like Minecraft. What can I do? Here’s what I’ve been trying:
Be different.
Whenever I see an opportunity to do something specifically unlike Minecraft, that doesn’t compromise what I’m trying to do, I take it and see what happens. However, most of these experiments have failed; for example, Cubes originally had a larger-scaled player character, but this turned out bad because it means tunneling and building is 8× more tedious, and it reduces the apparent size of the world. Also, it leads to thinking “OK, add this feature Minecraft has — but (superficially) differently!”
Be generic.
This is my long-term goal, and it is one that ties neatly into the “blocks made of blocks” theme. The characteristics of blocks can be defined by building circuits (programs) inside them. What I’m aiming for is that by creating a blockset (collection of block designs which the player can build with), one is defining the game that can be played, by giving those blocks specific behaviors.
In this way, I am working towards having a game which can be programmed to emulate Minecraft.
(I have a working prototype of an importer for Minecraft worlds as well as for Minecraft blocks — that is, turning the terrain.png from a Minecraft texture pack into Cubes' 3D blocks — but I am not going to release that code until and unless I determine that Mojang doesn’t mind my doing so. I still love Minecraft and they deserve my not stepping on their toes that far.)
However, this means both that Cubes itself needs to be very generic, and that the built-in example uses of such features should feel different from Minecraft.
So, returning to the original topic of “where am I going next”, I need to add the following functionality to the game world:
Extend the circuits feature so that there can be blocks that are
active and interactive (e.g. opening and closing doors, “physics” like Minecraft falling sand and growing plants).
Add moving objects (for vehicles and mobs). I intend to generalize
these so that they are worlds in themselves — this will allow large or unique vehicles, and mean that they can be designed using the same game tools.
Add some form of resource constraints/conservation laws (as in Minecraft survival
mode) — that is, you have to gather stuff to make it into other
stuff. I haven’t figured out specifically how I want to do this yet,
and this seems particularly tricky to make programmable. One idea that keeps coming to mind is that when you break a block, specific subcubes are “resource cubes” (according to their type in the block world) which you collect, and in order to place a block you need to have the corresponding resources for its type. However, I’m not sure I like the “raw material counter” feel of this.
Add player attributes that can be modified (e.g. health) so that e.g. death, or other effects-by-the-world can be supported.
Less grandly, I plan to work on one of these specific technical features soon:
Allowing circuit blocks to be rotated to change their connectivity. (Right now, circuit blocks have specific faces — e.g. on a certain one the +X direction is always the output.)
Figure out what more circuit primitives I want to add. (Right now, the circuits are definitely not Turing-complete, and not capable of all the effects on the world they should be, but there are also already a lot of different primitives; I may have to invent new block-picking UI just to make them practical.)
Add moving objects (bodies) — things which can collide with the terrain
as the player does. The current code is entangled with player
behavior, and the player does not persist in a world.
Add subworld/multiple-world handling — the ability for more than one
world (grid of blocks) to be present in the same space. Right now, there are hardwired assumptions that the player is
in the single world’s coordinate system.
Another core feature which is currently missing is the ability to design a blockset and then reuse it for multiple worlds. The problem right now is that we're using a simple object-graph serializer, so each world has its own blockset which is modified independently. To fix this, it needs to be possible to save a blockset under a user-visible name, and have individual worlds which reference that blockset; also, the world generator needs to decouple blockset generation from terrain generation. The “persistence” framework which added support for multiple worlds is a step towards this; the main thing I am pondering is what the semantics of these separate-named-persistent objects are and what the user interface for editing them is.
2012-02-03T23:35:29Z
Kevin Reid (kpreid)
kpreid@switchb.org
This is the start of my series on reliable techniques for writing fast Haskell code. In each installment we'll look at a specific situation and explore some simple guidelines you can use in your everyday programming.
Today's topic is forcing values returned from monadic computations. After reading this article you'll hopefully be able to tell when it's appropriate to force values passed to return and when it's not.
Consider this function:
f :: Int -> Int -> Int
f x y = x + y
f is strict in both arguments and when called with two arguments it will return a fully evaluated Int value. No laziness anywhere to be seen.
Now lets look at a function that looks very similar to f, but involves return:
g :: Monad m => Int -> Int -> m Int
g x y = return $ x + y
g looks very similar to f so it's easy to think that it, like f, is strict in both arguments and that it will return a fully evaluated Int value inside the given monad.
However, that's not the case. Lets look what happens if m is e.g. a state monad (such as IO) [1]. Here's a possible definition of a state monad:
newtype State s a = S (s -> (a, s))
instance Monad (State s) where
return x = S $ \ s -> (x, s)
m >>= f = ...
Lets expand >>=, return, and $ in g:
g :: Int -> Int -> State s Int
g x y = S $ \ s -> (x + y, s)
Since our monad wraps the return value in a (lazy) pair, the evaluation of x + y is delayed until the first component of that pair is evaluated, some time later in the program. If we don't want to delay the evaluation, we must first force the value passed to return (e.g. by using $!):
g :: Int -> Int -> State s Int
g x y = return $! x + y
Here's what we get if we expand the stricter version:
g :: Int -> Int -> State s Int
g x y = let z = x + y
in z `seq` S $ \ s -> (z, s)
Now the expression x + y is evaluated before the pair is created and the result (rather than a thunk representing the expression) is stored in the pair.
We can also see the difference between the two different versions of g in the core generated by GHC. First the lazier version:
g = \ (x :: Int) (y :: Int) (s :: s) ->
(case x of _
I# x# -> case y of _
I# y# -> I# (+# x# y#),
s)
and then the stricter one:
g = \ (x :: Int) (y :: Int) (s :: s) ->
case x of _
I# x# -> case y of _
I# y# -> (I# (+# x# y#), s)
If you look carefully you'll see that in the lazier version the evaluation of x and y has been pushed inside the first component of the pair.
The stricter version still allocates an I# box, because pairs are polymorphic in their components and unboxed values (like Int#) cannot be stored in polymorphic fields and thus need to be wrapped.
The above example used a simple arithmetic expression but the same reasoning applies to data types with strict fields. For example, given this strict-pair data type:
data StrictPair a b = SP !a !b
you need to use $! if you want its fields to be evaluated before a monadic computation returning a StrictPair returns, like so:
h :: Int -> Int -> State s (StrictPair Int Int)
h x y = return $! SP (x+1) (y+1)
Aside: Int (and Double, Float, etc.) arithmetic is really just a special case of a data type with strict fields as Int is defined as:
data Int = I# Int#
where Int# is an unboxed type. Fields of unboxed types are always strict.
Forcing values that are already in WHNF has no effect e.g.
-- Has no effect and thus potentially confuses readers:
h x y = return $! (x, y)
Guideline: force data types with strict fields (including e.g. Int) to WHNF using $! before passing them to return.
This behavior is not specific to the state monad. Any monad where return is lazy in its argument would behave the same.
How can we pick a random Haskell function? Specifically, we want to write an IO actionrandomFunction :: IO (Integer -> Bool)with this behavior:It produces a function of type Integer -> Bool.It always produces a total function — a function which never throws an exception or enters an infinite loop.It is equally likely to produce any such function.This is tricky, because there are infinitely many such functions (more on that later).In another language we might produce something which looks like a function, but actually flips a coin on each new integer input. It would use mutable state to remember previous results, so that future calls will be consistent. But the Haskell type we gave for randomFunction forbids this approach. randomFunction uses IO effects to pick a random function, but the function it picks has access to neither coin flips nor mutable state.Alternatively, we could build a lazy infinite data structure containing all the Bool answers we need. randomFunction could generate an infinite list of random Bools, and produce a function f which indexes into that list. But this indexing will be inefficient in space and time. If the user calls (f 10000000), we'll have to run 10,000,000 steps of the pseudo-random number generator, and build 10,000,000 list elements, before we can return a single Bool result.We can improve this considerably by using a different infinite data structure. Though our solution is pure functional code, we do end up relying on mutation — the implicit mutation by which lazy thunks become evaluated data.The data structureimport System.Randomimport Data.List ( genericIndex )Our data structure is an infinite binary tree:data Tree = Node Bool Tree TreeWe can interpret such a tree as a function from non-negative Integers to Bools. If the Integer argument is zero, the root node holds our Bool answer. Otherwise, we shift off the least-significant bit of the argument, and look at the left or right subtree depending on that bit.get :: Tree -> (Integer -> Bool)get (Node b _ _) 0 = bget (Node _ x y) n = case divMod n 2 of (m, 0) -> get x m (m, _) -> get y mNow we need to build a suitable tree, starting from a random number generator state. The standard System.Random module is not going to win any speed contests, but it does have one extremely nice property: it supports an operationsplit :: StdGen -> (StdGen, StdGen)The two generator states returned by split will (ideally) produce two independent streams of random values. We use split at each node of the infinite tree.build :: StdGen -> Treebuild g0 = let (b, g1) = random g0 (g2, g3) = split g1 in Node b (build g2) (build g3)This is a recursive function with no base case. Conceptually, it produces an infinite tree. Operationally, it produces a single Node constructor, whose fields are lazily-deferred computations. As get explores this notional infinite tree, new Nodes are created and randomness generated on demand.get traverses one level per bit of its input integer. So looking up the integer n involves traversing and possibly creating O(log n) nodes. This suggests good space and time efficiency, though only testing will say for sure.Now we have all the pieces to solve the original puzzle. We build two trees, one to handle positive numbers and another for negative numbers.randomFunction :: IO (Integer -> Bool)randomFunction = do neg <- build `fmap` newStdGen pos <- build `fmap` newStdGen let f n | n < 0 = get neg (-n) | otherwise = get pos n return fTestingHere's some code which helps us visualize one of these functions in the vicinity of zero:test :: (Integer -> Bool) -> IO ()test f = putStrLn $ map (char . f) [-40..40] where char False = ' ' char True = '-'Now we can test randomFunction in GHCi:λ> randomFunction >>= test---- - --- - - - - -- - - - -- --- -- -- - -- - - -- --- --λ> randomFunction >>= test- ---- - - - - - - -- - - --- --- -- - -- - -- - - - - - -- - λ> randomFunction >>= test- --- - - - -- --- - -- - - - - - ---- - - --- - - -Each result from randomFunction is indeed a function: it always gives the same output for a given input. This much should be clear from the fact that we haven't used any unsafe shenanigans. But we can also demonstrate it empirically:λ> f <- randomFunctionλ> test f- ----- - - -- - - --- -- - - - - - - -- - - ---- - - - - - --- λ> test f- ----- - - -- - - --- -- - - - - - - -- - - ---- - - - - - --- Let's also test the speed on some very large arguments:λ> :set +sλ> f 10000000True(0.03 secs, 12648232 bytes)λ> f (2^65536)True(1.10 secs, 569231584 bytes)λ> f (2^65536)True(0.26 secs, 426068040 bytes)The second call with 2^65536 is faster because the tree nodes already exist in memory. We can expect our tests to be faster yet if we compile with ghc -O rather than using GHCi's bytecode interpreter.How many functions?Assume we have infinite memory, so that Integers really can be unboundedly large. And let's ignore negative numbers, for simplicity. How many total functions of type Integer -> Bool are there?Suppose we made an infinite list xs of all such functions. Now consider this definition:diag :: [Integer -> Bool] -> (Integer -> Bool)diag xs n = not $ genericIndex xs n nFor an argument n, diag xs looks at what the nth function of xs would return, and returns the opposite. This means the function diag xs differs from every function in our supposedly comprehensive list of functions. This contradiction shows that there are uncountably many total functions of type Integer -> Bool. It's closely related to Cantor's diagonal argument that the real numbers are uncountable.But wait, there are only countably many Haskell programs! In fact, you can encode each one as a number. There may be uncountably many functions, but there are only a countable number of computable functions. So the proof breaks down if you restrict it to a real programming language like Haskell.In that context, the existence of xs implies that there is some algorithm to enumerate the computable total functions. This is the assumption we ultimately contradict. The set of computable total functions is not recursively enumerable, even though it is countable. Intuitively, to produce a single element of this set, we would have to verify that the function halts on every input, which is impossible in the general case.Now let's revisit randomFunction. Any function it produces is computable: the algorithm is a combination of the pseudo-random number procedure and our tree traversal. In this sense, randomFunction provides extremely poor randomness; it only selects values from a particular measure zero subset of its result type! But if you read the type constructor (->) as "computable function", as one should in a programming language, then randomFunction is closer to doing what it says it does.Edit: See also Luke Palmer's recent article on this subject.See alsoThe libraries data-memocombinators and MemoTrie use similar structures, not for building random functions but for memoizing existing ones.You can download this post as a Literate Haskell file and play with the code.
Pipes are a very simple but powerful abstraction which can be used to implement stream-based IO, in a very similar fashion to iteratees and friends, or conduits. In this post, I introduce guarded pipes: a slight generalization of pipes which makes it possible to implement a larger class of combinators. > {-# LANGUAGE NoMonomorphismRestriction #-} [...]
I was trying to build the curl hackage package on Debian unstable with ghc 7.0.3, and one of its modules were failing to build. I searched for the error message and found this GHC ticket. As mentioned in the ticket, I had to downgrade binutils to 2.20. The version of binutils in sid is 2.21.
Another possibility would be to change curl to avoid using -fvia-C, but I didn't want to modify the package.
2012-02-02T16:28:18Z
After testing Emacs, Eclipse, KDevelop and CodeBlocks for writing C++ code, I decided to stick with Netbeans. It seems to be the most simple to configure and yet full of features and plugins. I was having one problem with it that when I focused out the window, and then focused on it again, it would not really grab the focus, in the sense that I would not be able to type without first clicking with the mouse. At first I thought the problem was with Netbeans, then with the JRE. I tried using sun-java6-jre, I tried upgrading my openjdk-6-jre, and nothing worked. I searched a little bit more and got to a discussion about this problem in ion3, and that made me think that the problem could be related to the window manager I use, XMonad. After searching a bit about it, I found on the XMonad FAQ some work arounds for problems with Java apps, but they didn't solve my problem. Then, I found the solution on this bug report. I got the darcs version of xmonad and XMonadContrib, included takeTopFocus on logHook, and now it's working!
2012-02-02T16:28:18Z
Last year, we spent a lot of energy on reducing the memory consumption of vectorised Data Parallel Haskell (DPH) programs that use large shared data structures. This work was driven by an implementation of the Barnes-Hut algorithm in DPH. Ben produced an illustrative video animating an n-body simulation with his Gloss library. The video is part of his blog article Vectorisation without Replication in Data Parallel Haskell, where he explains the performance of our new DPH array library in comparison to our old library and a purely sequential implementation of Barnes-Hut based on Data.Vector.If you are interested in how vectorisation works, what the problem with shared data structures is, and how we are solving that problem, you may like to have a look at the slides of a talk that I gave in Copenhagen end of last year. It is available in two formats: HTML5 slideshow & PDF. Posted via email from PLS @ UNSW | Comment »
2012-02-02T11:16:35Z
Last year, we spent a lot of energy on reducing the memory consumption of vectorised Data Parallel Haskell (DPH) programs that use large shared data structures. This work was driven by an implementation of the Barnes-Hut algorithm in DPH. Ben produced an illustrative video animating an n-body simulation with his Gloss library. The video is part of his blog article Vectorisation without Replication in Data Parallel Haskell, where he explains the performance of our new DPH array library in comparison to our old library and a purely sequential implementation of Barnes-Hut based on Data.Vector.If you are interested in how vectorisation works, what the problem with shared data structures is, and how we are solving that problem, you may like to have a look at the slides of a talk that I gave in Copenhagen end of last year. It is available in two formats: HTML5 slideshow & PDF.
Permalink
| Leave a comment »
2012-02-02T11:16:32Z
Thanks to everyone for the very positive response about the upcoming Yesod book! There were various questions floating around the comments, Google+, Reddit, and email, so I just wanted to give as thorough a set of answers as I could.
The book will be available in paperback and ebook format, supporting Mobi and ePub.
For the ebook, you can get it directly from O'Reilly, which means you'll get updates. It will also be available from various other sources: Amazon, Nook store, iBookstore, Kobo, and more.
I know Amazon says the book is 100 pages; that was just an initial guess from who knows when. The book is close to 300 pages.
The book is being updated as we're making changes to Yesod. The goal is to release the book not long after Yesod 1.0. The book should cover that release.
The current book on this site covers 0.9. There's a beta version that covers 0.10.
Some people believe the book is imbued with magical powers, and can cure people of all ailments and turn lead into gold. That's absolutely ridiculous; lead can't be turned into gold.
If you have any other questions, let me know, I'll try to keep the post up-to-date with answers.
Here is a Barnes-Hut gravitation simulation written using Data.Vector and Gloss. If done naively, such an n-body simulation has a runtime complexity of O(n^2) because we need to consider the interaction of every body with every other body. Barnes-Hut performs an approximation that reduces this to O(n . log n). At each step in the simulation we insert the bodies (grey) into a quad-tree (green), and compute the centroid for each branch (blue). Now, if some other body is sufficiently far away from a centroid, then we use the centroid to approximate the force due to the bodies in the corresponding branch, instead of inspecting each one individually.Now you've seen the video, the following graph sums up my work on Data Parallel Haskell (DPH) for the past six months:This is a plot of runtime vs the number of bodies in the Barnes-Hut simulation. The simulation in the video uses just 1000 bodies, but my understanding is that the physicists need millions or billions to do useful physics work. Back to the graph, the red line is the program from the video, which uses Data.Vector to store its arrays and runs sequentially. The brown line is using the DPH library that shipped with GHC 7.2. The blue line is using the DPH library that will ship with GHC 7.4. Note how the asymptotic complexity of the program with New DPH is the same as with Data.Vector. In Old DPH we were using a naive approach to vectorisation that resulted in the quad-tree being replicated (copied) once for every body in the simulation (bad!). In New DPH we compute the force on each body in parallel while sharing the same quad-tree. There is a story about this, and you can read all about it when we finish the paper.Of course, asymptotic complexity isn't everything. The DPH program is still running about 10x slower than the Data.Vector program for large input sizes, and I'm sure that production C programs would run at least 5x faster than that. There's much low hanging fruit here. DPH misses many standard optimisation opportunities, which results in numerical values being unboxed and reboxed in our inner loops. There are also algorithmic improvements to the library that are just waiting for me to implement them. If I can make the blue line cross the red line in the next six months, then I'm buying everyone a beer.
In my last post I mentioned how it is possible to achieve a form of "reinversion of control" by using (green) threads. Some commenters noted how this is effectively a solved problem, as demonstrated for example by Erlang, as well as the numerous variations on CSP currently gaining a lot of popularity. I don’t disagree [...]
Yesterday, during a very nice presentation by Ohad Kammar at Carnegie Mellon, the discussion got derailed, in part, because of a standard, and completely needless, terminological confusion involving the word “variable”. I’m foolish enough to try to correct it. The problem is that we’ve all been taught to confuse variables with variables—that is, program variables [...]
We added a neon pixel heart design to our shirt store. It is based on one of the first game objects that you will see when playing Episode 1 of the story mode:
One of us was at the Global Game Jam in Berlin as an organizer.
The theme was a snake biting its own tail and we made a tiny level referencing it. Too late for submission though. ;)
video: Nikki and the Snake - GGJ 2012 Themed Level
The video also shows the recently added game over screen menu (no more abrupt returning to the level selection) and a new, simple background for the editor.
A quick message to say a new version of EclipseFP has been released. It's a bit of a rushed released, because a series of cascading updates meant that scion-browser and EclipseFP got out of sync. A while back I had made changes to the scion-browser API to improve performance, and then persistent got upgraded and a new compatible version of scion-browser got released on sunday. Unfortunately my API changes got released too, which meant a lot of headaches for the poor users that upgraded. So I've released 2.2.2 which is compatible with scion-browser 0.2.5.It does bring a few enhancements, though:- outline, tooltips and jump to definition should be much faster- HLint suggestions can be applied via the quick fix action on the suggestion marker- clean project does what it advertises: deletes the whole .dist-buildwrapper directoryAs usual, just use the update site from inside Eclipse to upgrade. Please report all bugs on the Source forge forum or tracker.Happy Haskell hacking!!
Hello,I just uploaded a new tutorial which covers three distinct, but related topics:acid-state - a native Haskell, noSQL, RAM-cloud databaseIxSet - a multi-indexed collection type (similar to Data.Map but with support for multiple keys)data-lens - a library which provides syntax that makes it easier to update nested records and other data typesThe tutorial is available here:http://happstack.com/docs/crashcourse/AcidState.htmlThe section on lenses is a great applied introduction to the concept which requires no understanding of Happstack, acid-state, or IxSet. So, even if you don't care about acid-state, you may find it useful if you have ever wondered what the heck lenses are.
I just released accelerate 0.9.0.0 on Hackage. This is the version that has been available from the GitHub repository for a while (supporting shape polymorphism, stencil computations, block I/O, and much more), but adapted such that it works with the forthcoming GHC 7.4.1 release. (I tested it with 7.4.1 RC2).It doesn’t yet include Trevor’s recent work that improved the CUDA backend in many significant ways — you can get that code from Trevor’s fork on GitHub.For more details, see the main GitHub repository and the GitHub wiki pages. Posted via email from Just Testing | Comment »
2012-02-01T05:55:23Z
For my own purposes, I wanted to take a tour of all the various formulations of
lenses on hackage and thought I would post the lightly-curated results here. I
just grepped the package list page for the following terms: lens, label,
record, accessor, editor, and reference. Some were not sufficiently general to
include here.
Here were the ones implementing "lens-like" functionality, loosely in order of
my opinion on their usefulness for my own purposes:
fclabels: straightforward, lenses built on underlying Point type, with support for lenses that can fail in ArrowZero
data-lens: uses natural a -> (s -> a, s) formulation for lens, leveraging category theory abstractions from the packages created after the breakup of Edward Kmett's category-extras (a.k.a. the Big Bang)
partial-lens: the above but suppporting failure on the container
data-accessor: package description has nice background, implementation (not exported, oddly) is same as Data.Lens, apparently used to have wonky State monad implementation, seems from a similar school as "lenses" package
state-record: Lens type composed of simple setter/getter function pair, with TH generator and misc. functions for use with State monad
lenses: based on MonadState/StateT, odd formulation with no concrete type representation for the lens itself
edit-lenses: implementation of ideas from this paper with demos here, rather... involved
pointless-lenses: based on this paper. also rather involved, for instance here is a Lens (Tree a) [a]
recursion-schemes: another of EK's packages. there's allegedly a lens hiding here among the zygohistomorphic prepromorphisms (you think I'm joking)
For an analysis of various approaches to lenses which don't necessarily have an
implementation on hackage, these slides
by Twan van Laarhoven were quite good, even without the accompanying talking
human. Conal Elliot's Semantic editor combinators
pattern seems also relevant to mention here.
Here's a list of the reverse dependencies
for these, as a guage of popularity/general usefulness:
data-accessor 46
fclabels 33
data-lens 12
pointless-lenses 3
recursion-schemes 2
What about record-y packages?
As a bonus, here are the packages which attempt to augment or improve on
records. I didn't spend much time trying to grok these:
ixset: fancy indexable, groupable records gone wild
records: records as groupable name/value pairs, perhaps similar to ixset
has: entity-based records, looks novel but don't understand it after a quick look
grapefruit-records: record system for grapefruit FRP, haven't looked at implementation
fields: high-level interface and cute syntax for record operations, built on fclabels, unmaintained
setters: just generates "setter" functions for record fields using TH
My own thoughts
The appeal of lenses for me is that they offer a first-class setter/getter on
which one can build abstractions
and more powerful libraries, not simply save the programmer some key strokes.
Lenses that simply throw an error when applied to the wrong constructor are not
suitable for this purpose.
IMHO the only package above that provides a straightforward lens type for real
algebraic data types is the seemingly unused 'partial-lens' variant on
Data.Lens. In which the simplified representation is:
newtype Lens a b = Lens (a -> Maybe (b -> a, b))
The 'fclabels' package is great, but handles failure on set only w/r/t both
the outer value and the inner value to be set. This means we can perform
validation on a data type with its setter (cool!), but means we
cannot partially apply a lens and get back a pure setter b -> a which is a
real problem for me.
Consider a modification of the Bubble Babble digest algorithm to emit words of 25 letters at a time (13 consonants, 12 vowels) instead of 5 at a time. Then, arrange the letters into 5 by 5 blocks:
TuCaL
oTiBe
KoTiF
oGiMu
FuGiH
This way, the 5-letter subwords can be read and verified both across and down as a doublecheck.
Inspired by the sator square in Latin, but omitting the capability of reading the same across and down (or being real words). We keep the checkerboard pattern of consonants and vowels.
With 16 consonants and 5 vowels to choose from, (omitting x and y from original Bubble Babble), one can fit up to log2(1613 * 512) = 79 bits per block.
While the original Bubble Babble tried to do error detection, let's separate concerns and assuming error detection or correction is done at a different level. Similarly, let padding be done elsewhere, too. We assume the message is a multiple of 79 bits.
Let n be a 79 bit integer. Let c = n mod (16^13), i.e lower 52 bits, and v = n div (16^13), i.e., upper 27 bits. Write c in base 16 using the alphabet ( b c d f g h k l m n p r s t v z ), and write v in base 5 using the alphabet ( a e i o u ), then interleave into a square.
One may consider consonant clusters, consonants from other languages (clicks, aspiration), diphthong vowels, vowel tones, but things get messy, especially with consonant clusters. Some consonants can only be in certain places of a word. Here is one attempt, implemented in Haskell. This is well on our way to a random word generator, though it often creates nearly unpronounceable words, probably due to the excessive consonant clusters.
Vowels: a e i o u ai ei oi au á à é è í ì ó ò ú ù ái ài éi èi ói òi áu àu
Consonants: K! T! P! R Y H W ñ Kn Gn Br Dr Fr Gr Kr Pr Tr Vr Bl Fl Kl Pl Sl Vl Ð nD nG nK nT nZ mP rJ rS rZ rČ rŠ rθ nS mB nL rB rD rF rG rK rM rN rP rT rЦ rV rχ J S Z Č Š θ B D F G K L M N P T Ц V χ sT sK sP Ξ Ψ
Number of choices by grid position:
4627452724
2772277227
4527722736
2772277227
2427362739
This allows 128 bits (129.21072040507875) to be encoded in a single block. Sample:
RèMuŠ
uΨàuÐé
ñáiЦáiΞ
òθoinGì
χòrČòiP!