Politics, programming, math, and science.
Computers
Why I Love Currying
Feb 12th
So I’ve been playing around with Haskell a lot lately and using it for various random stuff; I haven’t progressed to the point where it’s my go-to language for random programs (I still use Python for that), but I at least have an idea of how to use it. And there’s one feature of Haskell that I miss sorely when I write code in Python, or pretty much any other vaguely functional language: currying.
In Haskell, every function takes a single argument. A function of multiple arguments, such as map, which applies a function to every element in a list, actually only has one argument; for example, map can be interpreted either as taking a function and a list and returning a list, or as taking a function and returning a function that takes a list and returns a list. More formally, in Haskell, these two type declarations are equivalent:
This process, of taking a multi-argument function and converting it into a series of single-argument functions is known as currying, after the mathematician Haskell Curry (who, obviously, is also the source of the name Haskell); the process of partially applying arguments to a function in this way is known as ‘partial application’, but is also called currying. One of the most obvious examples of currying is in sections: the function (0 ==) is syntactic sugar for (==) 0, and returns whether its argument is equal to zero. Furthermore, we can also partially apply the predicate to filter, to make a function that filters its argument on a fixed predicate. So, these three examples are completely equivalent:
(where /= is Haskell’s not-equal operator). The first is the most explicitly-written version, using no currying at all. The second curries the predicate; (/= 0) x is the same as x /= 0. Finally, since removeZeroes applied to an argument is the same as applying filter (/= 0) to it, we might as well define the former as the latter. Or, to take another example, look at the sortBy: it has type (a -> a -> Ordering) -> [a] -> [a], where Ordering is a datatype that can either be EQ, LT or GT for equal, less than, or greater than. So if you have some custom function you want to sort a list on, you can just say mySort = sortBy f and it will be the same as writing mySort xs = sortBy f xs, only cleaner and neater. Or in my Data.Nimber module (specifically lines 38, 39, and 43), many operations on Nimbers that’re required in order for me to call then ‘numbers’ are just the identity operation. So instead of saying abs x = x, I can just say abs = id.
Furthermore, without currying, you couldn’t have variadic functions; in order to work inside Haskell’s type system, the two types a -> b -> c and a -> (b -> c) have to be the same type. The full explanation involves typeclasses, and is (in my opinion) worth a read, because it’s a good explanation of a pretty horriblexcellent (it’s both at once, you see) type system hack.
As an aside, this also means that id :: a -> a, the identity function, is in a sense the same thing as ($) :: (a -> b) -> a -> b, which is function application. You can see this by substituting (b -> c) for a in the type of id, then removing parentheses:
So, in particular, f `id` x is the same as f $ x, which is just f x. Another way to think of this is that f `id` x = id f x = (id f) x = f x.
Variadic Functions in Haskell
Jan 12th
Most modern languages have some kind of printf analogue: a function that takes a format string, and a series of things to be inserted into that string, and formats them all accordingly. At first glance, Haskell’s strong type system would seem to preclude this. There’s no built-in system for writing functions that take variable numbers of arguments, and it seems like it would be difficult to write one. The standard approach is to take a list instead, but this fundamentally doesn’t work for printf, since you’re going to be wanting to print Integers, Strings, and Floats. It’s possible to just pre-apply show to everything, but that’s not really a good idea, because you might want to show them in a different way than the built-in show does. You can use an extension called existential types to create a list of PrintfWrappers which wrap integers/floats/strings (more on that below), but that requires your users to manually do the wrapping, which is, once again, not a good idea. Haskell’s Text.Printf module takes a third approach. Look at the following lines:
Here’s how to interpret this: PrintfType is the type of things that can be printed to. Printing to a String just gives you a string, much like sprintf in C or Perl, printing to an IO () will actually print it out (so you can use it like a normal printf in do blocks, a behavior which I personally find distasteful.). However, printf will return undefined when asked to return an IO r; the reason that you can nevertheless return one is that only declaring IO () as an instance of PrintfType is invalid according to Haskell 98.
PrintfArg, by comparison, are the elements that are valid arguments to printf; they basically consist of the various WordN/IntN types, Integer, Float, Char, and (IsChar c) => [c]. The point of the last instance is that, while you can’t have a specific version of a polymorphic type be an instance of a typeclass, you can restrict it to types whose parameters are themselves instance of another typeclass; the only instance of isChar is Char.
So now that we have that clarified, let’s suppose we want to call printf with “%s %d %f” “foo” 42 3.1, passing it the format string, String, an Integer, and a Float. This causes printf’s type to become
Does this match the pattern (PrintfType r) => String -> r? Let’s go in reverse. String is an instance of PrintfType, and Float is an instance of PrintfArg, so Float -> String is an instance of PrintfType. Therefore, Integer -> (Float -> String) is an instance of PrintfArg, and so is String -> (Integer -> (Float -> String)). Dropping parentheses, this becomes String -> Integer -> Float -> String. So the types all check out. If you pass an invalid type, then you’ll run into something that isn’t an instance of PrintfArg and so the types won’t check.
I mentioned above that if you use something called ‘existential types’, you can do something similar. The way it works is that you define a new type whose data constructor only requires that its argument be of a given typeclass. Look at the following example
When you run showBoxes boxes, you get 2 "f" 83, exactly as you’d expect. Note that, however, the function unbox (Box x) = x cannot be written; it would have to be of type (Show s) => Box -> s, and there’s just no real way to do that. So once you’ve wrapped something up in a Box, you can only get at it by showing it. From this, you can see how to pass a heterogeneous list to printf. The reason that this approach is suboptimal is that it would require Text.Printf to export a Printf data constructor which would wrap up everything to make it of the appropriate type, and that would be rather annoying, especially since it relies on show preserving enough information for you to format the number after reading it back in.
This pattern can obviously extended to any other variadic, heterogeneous function, as long as you can define a suitable typeclass that its arguments must all be instances of. And that’s not really a restriction at all; if you can’t specify a behavior that the instances must have, then you don’t really know what you can do with the arguments, and so you can’t do anything at all!
Data.MemoCombinators and You
Nov 22nd
Part of the beauty of Haskell is that it allows you to simply write recursive functions. But part of the problem with recursive functions is that they tend to have absolutely horrible big-O run times. The usual solution to this problem is to use what’s known as memoization, which is memorization without the ‘r’, since programmers have to have special names for everything. Memoization is usually implemented as an associative array (or a plain array in the common case where the function takes a single non-negative integer as an argument); the function attempts to look up the return value for its arguments in an associative array. If it finds it, it can return without doing expensive computation; if it doesn’t, then it performs the computation, stores the result in its array, and then returns. In Python, a memoized Fibonacci function might be written as follows:
def fib(n):
if (n < 2): return n
if n not in fib_cache: fib_cache[n] = fib(n-1) + fib(n-2)
return fib_cache[n]
The speed savings gained by this are enormous; on my test machine, fib(35) takes 15 seconds to compute without memoization, whereas fib(1000) computes almost instantly with memoization. In terms of big-O running times, I believe that the memoized version takes
time, whereas the unmemoized version takes
, which is interesting since the Fibonacci numbers themselves are
. In any case, the memoized version is clearly superior.
But how do you do this in a language such as Haskell? You can’t carry state between the various incarnations of the function, since that could potentially lead to the function’s values not solely depending on its arguments, violating referential transparency. You can’t carry the state around in a monad because then different calls to the function would each have separate caches, so you’d have to pull some kind of trick where the function returns itself and its value, then pass the function around and it would just be a huge mess. So instead what you do is you use Data.MemoCombinators, which is a package that lets you turn functions into other, memoized functions. So how do you use it? It’s not too hard, especially if you’re memoizing functions that only use builtin types. An example, straight from the Data.MemoCombinators page:
where
fib' 0 = 0
fib' 1 = 1
fib' x = fib (x-1) + fib (x-2)
There are two things to note here: first, the memoized version, fib, is generated from the non-memoized by calling Memo.integral on it. This is how you create memoized versions of single-variable functions: you apply the appropriate combinator. Second, fib’ calls fib inside it. This is very important: if fib’ called fib’, then you couldn’t save time within the fib function, only outside of it. With fib’ calling fib, on the other hand, then the first time you call fib 1000, not only will it return before the heat death of the universe, but you’ll also get fib 999, fib 998, etc. cached.
But what if your function to be memoized isn’t one of the standard types? That’s why there’s Memo.wrap. You just have to define two mappings: one from your type to some combination of MemoCombinator types, and one that goes from that combination back. An example will make it clear:
So as you can see, first you build up a memoString type which can memoize Strings; since a String is just a list of Chars, you can just apply Memo.list to Memo.char. Then you define toFoo and fromFoo, which send you from the abstracted Foo type to a tuple of a String and an Int. Finally, you use Memo.wrap to ‘wrap’ the pair of a memoized String and a memoized Int (constructed using Memo.pair, naturally) up in an abstract memoFoo memoizer.
The other thing you can do with MemoCombinators is memoize functions of multiple variables. Take this sample of code from a project I’m working on:
(*) = Memo.memo2 memoNimber memoNimber (*!) where
x *! (Nimber 1) = x
(Nimber 1) *! x = x
a *! b = mex $ liftM2 combine [0 .. pred a] [0 .. pred b]
where
mex xs = fromJust $ find (`notElem` xs) [0..]
combine a' b' = a * b' + a' * b + a' * b'
The actual definition of *! isn’t important, I’m only including it for completeness. Nor are the definitions of toNimber and fromNimber. What is important is Memo.memo2: you use it to generate a memoized function of multiple arguments. You just pass it memoizers for each of its arguments (since * takes two Nimbers, I pass it memoNimber twice) and the unmemoized version, and it gives you a memoized version.
As for how Data.MemoCombinators works, I can’t really explain that. I know it has to do with the fact that expressions in function definitions are cached, but beyond that my knowledge fails. Maybe if I ever learn it I’ll return to this and explain it.
Edit: After I wrote this I realized that Data.MemoTrie exists; while it has cleaner syntax for memoizing functions (the memoizer doesn’t need to know the types of the arguments), it has a disadvantage in that it’s not immediately obvious how to memoize the types it doesn’t give you. But if you’re just memoizing functions of Ints or something, go ahead and use MemoTrie.
Properly starting Compiz in GNOME
Aug 20th
The typical method under Ubuntu of setting fusion-icon, a Compiz manager that gives easy access to the Compiz settings manager, emerald themer, and window manager switcher, is to add an entry in Startup Applications. This isn’t an unreasonable assumption, but it turns out to not be optimal; among other things, it causes GNOME Do to drop into its standard, non-composited theme as it winds up starting before fusion-icon can launch compiz. Instead, add the line
to your ~/.gnomerc file, creating it if necessary. This way, GNOME will start compiz and never launch metacity to begin with, saving both login time and the effort of relaunching Do.
Note that if you do have an entry in Startup Applications for fusion-icon, you should edit it to
so that it won’t launch another Compiz instance itself.
Keep / and /home on a Separate Partition
Aug 13th
The title pretty much says it all; if you’re running Linux, I highly, highly recommend that you put /home on a separate partition of your hard drive than the root partition /. Why do this? The biggest reason is that in case you mess up your operating system and need to reinstall it, then you only need to overwrite the / partition, leaving your data in /home fully intact. Since most applications store settings in some su<b>b</b>directory of your home directory, you’ll even get to keep your settings. How do you do this? Simple: when you’re installing the operating system, specify an additional partition to be used as /home. You also might want to set up a swap partition with size about equal to your RAM, but you don’t strictly have to. As for sizing the root partition, I’d go with about 20GB or so; most applications don’t take up a lot of space. If you’re on a SSD and 20 GB is a lot of capacity, you can probably get away with 10; I wouldn’t go any lower than that, though.
Now, the natural question is: how do you set up /home on a separate partition if you already have it on the same partition as /? It’s possible, but dangerous; if you mess this up, you can leave your system in a state that would require a good deal of knowledge to recover. You should be fine as long as you follow these instructions, but I can’t guarantee anything, obviously. I also highly recommend you have an external hard drive of sufficient size to contain your current /home directory (which you can find out via du -sh /home ).
- Create a gparted CD or USB drive. You can’t modify a partition that you’re currently using, and you can’t unmount the root partition while you’re using that operating system. The natural solution: create a LiveCD or USB drive. First, you’ll need to download GParted from here. You can use brasero to burn the CD (pick ‘Burn image’) or usb-creator (both of which are likely in your repositories if you don’t have them already) to create a bootable USB drive from the .iso.
- Reboot and open up GParted. You should boot into the GParted CD/USB. If not, you did it wrong.
- Open up a terminal and GParted. Self-explanatory.
- Mount your current partition. Figure out which device is your Linux partition; look at the GParted window and take note of the entries in the ‘Partition’ column; your Linux partition will likely have filetype ext3, or ext4. For simplicity’s sake, I’m going to assume that it’s /dev/hda1, although you’ll likely have a different one. In the terminal, type the following:
mkdir /tmp/linuxpart
mount /dev/hda1 /tmp/linuxpart - Mount your external hard drive. For the sake of this tutorial, I’m assuming it’s already formatted to something like ext3 and it only has one partition. If you only have one internal drive, then you’re going to be using /dev/sdb1, if you have two, then /dev/sdc1, etc.
mkdir /tmp/exthd
mount /dev/sdb1 /tmp/exthd
mkdir /tmp/exthd/newhomeObviously, if the exthome directory exists already on your external, use a different filename.
- Copy. You can’t just use a plain ‘cp -R’ for this; you’d lose a lot of information. Instead, do
cp -avu /tmp/linuxpart/home/* /tmp/exthd/newhome
This will preserve various stuff, such as symbolic links. It’ll also take a while, so find something else to do.
- Delete. This is the moment of truth; so far, everything that you’ve done is harmless to your drive. Once you do this step, you’re essentially committed.
rm -rf /tmp/linuxpart/home/*
- Unmount. Unmount the internal hard drive with
umount /tmp/linuxpart
This will let you resize it. Don't unmount the external hard drive, though. - Shrink your old partition. Once that's done, you can right-click on the partitions in GParted to manipulate them; it's fairly self-explanatory. Right-click the main home partition, select Resize/Move, set it to however large you think you'll need (I recommend 10-20 GB).
- Resize/move your new partition. Right-click your new partition, pick Resize/Move, and stretch it out to fill all of the available space.
- Execute. So far, you've only told GParted what you want it to do; once you click 'Apply', it'll do all the formatting and resizing and such. Give it a while, especially if you have a big hard drive. Once it's done, make note of the partition device; I'll assume it's /dev/hda2.
- Mount the new partition and copy files back.
mkdir /tmp/homepart
mount /dev/hda2 /tmp/homepart
cp -avu /tmp/exthd/newhome/* /tmp/homepart - Edit your filesystem table. This is important; without this, your OS won't know where to find /home . In the terminal, run
mount /dev/hda1 /tmp/linuxpart
nano -w /tmp/linuxpart/etc/fstabYou should see a file pop up; you can ignore the lines that are already there and add one at the bottom that looks like this:
/dev/hda2 /home ext3 relatime 0 2Obviously, make sure to replace /dev/hda2 with whatever device your home partition is on; if you chose a filesystem other than ext3 for /home, then change that as well.
- Reboot You're done now; reboot, take the GParted CD out, and boot back into your freshly-partitioned Linux installation
Hopefully, this has all gone well and you now have /home on a separate partition. If it hasn't, well... that's why you make backups. You did make backups, right?
Linux on the MSI GT725
Aug 11th
So I finally managed to get my sound to come out of speakers other than the bass on my new GT725 in a way that; it’s not 100% perfect, as I think that the subwoofer isn’t being used, but it’s good enough for me; if you’re an audiophile, you shouldn’t be listening to anything through built-in laptop speakers anyway. The graphics fix I detailed in this post, so I’ll share the fix I found for the soundcard. It’s fairly simple; in a terminal, run
where soundfix.conf can be named anything you want, and then paste these three lines:
alias snd-card-0 snd-hda-intel alias snd-slot-0 snd-hda-intel options snd-hda-intel model=targa-dig
into the file. Reboot and you should have sound that doesn’t all come out of the bass speakers. If you want, you can replace targa-dig with one of the other models listed under the ALC888 section here; if one of them works better, let me know in the comments and I’ll edit it in.
Fixing graphics lag on ATI graphics cards with Compiz
Aug 10th
I recently picked up an MSI GT725 series laptop for portable gaming, and I’m fairly happy with it, although I haven’t had it for too long. One issue that irked me when I first set up Linux on it alongside Windows 7 is that when I enabled Compiz, many things would lag, such as window resizing, window moving, and the opening/closing of Tilda and Yakuake. It turns out that this is due to an old patch for xserver never having been included due to the fact that it causes corruption on Intel cards; however, since I wasn’t using an Intel card, I was able to apply it with no problems. Although the patch can be applied to the raw xserver source code, a much better way to fix it for Linux distros that use apt-get is to use a pre-patched version: simply add the two lines
deb http://ppa.launchpad.net/ubuntu-x-swat/xserver-no-backfill/ubuntu <span id="series-deb">jaunty</span> main
deb-src http://ppa.launchpad.net/ubuntu-x-swat/xserver-no-backfill/ubuntu <span id="series-deb-src">jaunty</span> main
to /etc/apt/sources.list (as root, obviously); then, add the key via
Update your repository information via
, then upgrade all the packages that come up as needing one. Reboot and you should be golden.
On another note, I’ve been unable to get the sound to not come out of the bass speakers on the laptop; as a result, everything has an excess of bass, which makes it sound absolutely horrible. If anybody has any idea what’s causing this, help would be much appreciated.
Irssi, ssh, and screen: Three great tastes that go great together
Jul 22nd
If you’re a total IRC addict like I am, then it can be extremely handy to have a client running 24/7 in case something interesting happens and your computer is off or whatever; if you have logging on, then you can just scroll through the logs. But the only way I know of to do this is to use Irssi, a terminal-based IRC client for Linux/Unix (although OSX ports do exist, and it can be installed on a Windows machine using cygwin). The long and short of it is: you SSH into a remote server and run Irssi under a screen session. If you know what all that means, great, you can go do that now; skip down to the ‘configuration” section below. If not, I’ll walk you through it step by step.
Setup
- Get a shell account on a server somewhere. This very well may be the hardest step for some; I don’t know offhand of any easily-obtainable free servers that will allow you to run Irssi under screen. Most of them either forbid long-running processes or any sort of IRC out right, due to abuse. I happen to know someone who gave me a shell account on their server, so I can’t speak as to the reliability of any free/paid servers that might be out there. If you have a computer that’s always on and runs Linux, you can install openssh via your distribution’s package manager and use that instead. Make sure to forward port 22 on your router, obviously.
- Get Irssi installed on that server. If you have admin rights on the server, then you can just install it via whatever package manager; otherwise, you’ll have to ask the admin nicely to do this for you.
- Start up Irssi under screen. Connect to the server and run screen -U; if all goes well, you should see a totally blank screen except for a new prompt. You are now inside screen, which is like a subshell inside your existing shell, only it won’t quit when you close the window. To get out of screen, press Ctrl-a and then d; to get back in, run screen -raAdU. You can make new ‘windows’ inside screen with Ctrl-a, c (meaning ctrl-a followed by a c) and delete them with Ctrl-a, k; use Ctrl-a, n and Ctrl-a,p to switch between windows. Once you have a screen window set up, just run irssi.
An Irssi primer
If you know how to work irssi, you can skip this part and go on down to the Configuration section. If you don’t, read on.
Irssi supports all the usual commands: /whois, /msg, /join, /connect, etc. But there are two major differences you’re going to have to get used to. The most obvious one is that, by default, you can only have one window visible at a time. You can switch via /win <number>, or by pressing Alt followed by the number of the window (using 10 for window 0); the letters q through o on the keyboard are mapped to windows 11-19. You can move windows around by switching to whatever window and using /win move <number>, and close windows with /win close <number> or just plain /wc <number>. Omit the <number> to close the current window. Alt-a will move you to a window with ‘activity’; I’m not sure how it picks which window to move you to though.
The statusbar indicates which windows have had some sort of activity since you saw them last; dark green indicates a /join or /part or something similar, white indicates someone actually saying something, and hot pink means that someone said your name or someone PM’d you. But I use a script called adv_windowlist, and I recommend you do too; it makes keeping track of windows much easier.
The other thing that you’ll need to keep in mind when using Irssi is that whenever you tell it to join a channel, open up a query with a person, etc., it’ll join the channel on the network you’re currently on, if you’re in a window with a channel or query open, or if you’re in the special status window (window 1), whichever network’s name is specified in the statusbar. You can switch the latter by pressing ctrl-X.
Configuration
Half the reason that you might want to set up in this way is so that you can log every conversation; useful in case there’s a dispute in a channel. Irssi doesn’t log by default, but you can easily enable it: simply /set autolog on. You can chagne the timestamps in the log by adjusting log_timestamp; I personally use “%H:%M:%S ” (without quotes, note the space after the S!), which produces lines of the form
23:31 <@Waxx> No magic ever, under any circumstances.
You can also set the irc autolog path via the variable autolog_path; it accepts the special variables $tag, which is the name of the server the channel/query is on, $0, the name of the query or channel itself. It also accepts all the special strings listed here, although the ones you’re most likely to use are %Y for the four-number year, %m for the two-digit month, and %d for the two-digit day. So, for example, an autolog_path setting of ~/irclogs/$tag/%Y/$0-%m-%d.log would produce logfiles at, say, ~/irclogs/synirc/2009/#site19-07-22.log.
One of Irssi’s key strengths is its ability to be customized by the end-user via perl scripts. Installation is simple; put them in your scripts directory, ~/.irssi/scripts, and then run them via /load scriptname.pl. If you want them to automatically run, put them in ~/.irssi/scripts/autorun and run them with /load autorun/scriptname.pl. The twothat I couldn’t live without are:
- splitlong.pl: Automatically splits long lines you copy-paste into Irssi to get around IRC’s character line limit. Install and forget; no configuration necessary.
- adv_windowlist.pl: Converts the window list into something actually useful: lists all your windows in a grid, along with their names. After downloading, running, and installing it, you might as well remove the standard activity statusbar itemwith /statusbar window remove act as it’s now redundant. You can modify how the window looks by following the instructions in adv_windowlist.pl; personally, I use
/set awl_display_key $Q%K|%n$H$C$S
/set awl_block -15to pad each window’s entry out to 15 symbols and to make each entry be hilighted appropriately as well as have the alt-keymap for that window (if available) prepended to the window name or have the window’s number (if not). Regardless, you should run
/statusbar window remove actto remove the ‘activity’ window, as adv_windowlist.pl renders it redundant.
There you have it; now you should be able to use irssi proficiently. As always, leave questions in the comments. I haven’t covered some of the other stuff you can do, such as theming and setting up automatic rejoin, but this post is getting long as it is and so I think I’ll put those in another post. Happy IRCing!
5 Reasons to Jailbreak your iPhone
Jul 16th
Jailbreaking. Everybody who’s had an iPhone for more than a few days and is remotely tech-savvy probably knows what it is: the process of modifying your iPhone’s software to allow code unauthorized by Apple to run on it. But why would you ever want to do such a thing? In no particular order:
- Themes. The standard iPhone OS doesn’t include a way for you to change the background on your iPhone, or the lock screen, or to personalize your iPhone in any way. WinterBoard, which is a replacement for SummerBoard, itself named after the SpringBoard (the ‘home screen’). Themes let you change pretty much anything about the way your iPhone looks.
- Tethering. Strictly speaking, this doesn’t involve jailbreaking, but given that it breaks in iPhone OS version 3.1, it’s assumed that Apple/AT&T doesn’t want you to do this. Simply open Safari, navigate to http://help.benm.at and select your carrier, then install the profile. Enable tethering under Settings -> General -> Network -> Internet Tethering, and you’re set.
- SBSettings. SBSettings is one of those things that doesn’t seem useful until you need it, and then it’s a lifesaver. Available in Cydia, and with its own section for plugins, SBSettings lets you enable/disable Bluetooth, 3G, Wifi, location tracking, and essentially anything you can toggle from the standard Settings.app, without ever leaving your own app. You just activate it by swiping sideways on the status bar, and set the toggles.
- Cycorder. If you’re without an iPhone 3GS, you can use Cycorder, available through Cydia, to record video. Even if you do have a 3GS, apparently Cycorder has better video quality, at the cost of larger video files.
- Running apps in the background. Pretty self-explanatory; install Backgrounder from the Cydia app repository (make sure to install the one appropriate for your software version; don’t install the 2.x on a 3.0 or vice versa!). Note that if you’re on a 3GS and you hold home down to activate voice control, that will enable backgrounding on that app (since Backgrounder uses a shorter timer than Voice Control), so you’ll have to disable it again afterwards. Or you could always exit the app and then activate Voice Control.
So how do you jailbreak? Install redsn0w, from the iPhone Dev Team (who’re obviously not the actual guys who dev for the iPhone). You can find torrents of it here; they’re all heavily seeded, so you shouldn’t run into any issues with respect to speed. It’ll work on any iPhone that’s not running the 3.1 beta builds, including 3GSs. If you’re running into issues, or something breaks, you can always put your iPhone into DFU mode. How do you do that? Simple.
- Plug it into your computer with iTunes.
- Turn it off. If it’s completely frozen, hold the power and home buttons down until it turns off.
- Connect it to your computer.
- Hold down the power and home buttons again.
- When the Apple logo comes up, release the power button; keep holding the home button until iTunes recognizes it and asks you to recover it.
Be aware that this will delete everything you have on the phone; so I recommend you back up often, especially with a jailbroken iPhone.
Gnawwy 0.0.2 out!
Jun 28th
And you said I couldn’t do it. Actually, so did I. I managed to motivate myself enough to get off of my ass and release version 0.0.2 of gnawwy, my little program I wrote to notify Linux users (specifically, Ubuntu users) of updates to Twitter and such via notify-osd. The big changes in v0.0.2 are e-mail account support, support for multiple accounts (Twitter and e-mail), and the movement of password information and such into a .gnawwyrc. The next version I plan on adding some actual error checking and debugging information, so I plan on calling it the actual v0.1.0 release. Now that I’ve motivated myself to get to work on this, I should actually have that out within a week or so. Still yet to come at some unspecified time in the future in no particular priority:
- Notification methods other than libnotify (perhaps even Windows support!)
- RSS feeds
- GUI configuration
- A tray icon to notify you of unread messages
- Actual comments in the code
- A shiny icon
