| MoMonkey 的个人资料Monkey Coder日志列表网络 | 帮助 |
|
|
8月19日 HOF + Purity = ConcurrencyI had fun giving a talk Monday evening at the Spokane .NET User Group. Here's the deck if you're interested. 4月30日 Turtle SoupI've been reading Seymour Papert's Mindstorms book about Logo; very interesting! I'm just dying to add Turtle Graphics to my little toy language from last post (BTW, a friend suggested I call it "Ape" and I think I will!). Logo with a twistWhat I wanted was essentially Logo but not exactly. The "turtle" starts in the center of the canvas facing up (North). It can be instructed to turn "right" or "left" or to move "forward" or "back". It can also be asked to draw a "line" to its current position. I didn't want to have some arbitrary distance for movement (turtle steps) or an arbitrary number of degrees for turning. Instead I did something a little strange :-) Everything is based on a unit circle in the center of the canvas. Moving "forward" will traverse the radius. Each move though divides the distance in half. So, for example, moving "forward back back back" will move from the center to the edge of the canvas, then "back" half of that (halfway between the center and the edge), "back" again will move another quarter, then an eighth. The net result is the same as moving forward an eighth of the radius. The same is true for turning "left" and "right". The first turn does a 180 (or a pi if you think in radians). The second makes a quarter turn, then an eighth, etc. So something like "right left left" is the same as turning clockwise 45 degrees. The move/turn deltas reset whenever a non-move/turn instruction is executed. Additionally, you can "stop" to reset. Also, you can return to "home" position (center facing up) any time. Sheesh, this is more difficult to explain in English than to just write the code already! 30-line ImplementationI didn't want to add new primitives to the language. If you read the last post, you know I quite like having boiled the language down to it's essence with just four primitives. Instead, think of Turtle Graphics as an output format. Rather than just printing a resulting symbol tree to the console, we paint a sequence of "turtle instructions." The AST passed in is not a tree. It's just a single flat list of Symbols. open System.Drawing
Take the Turtle for a SpinWe can now execute simple Ape programs and feed the output to this little rendering engine. For example: forward back line right left left forward back line Cool! That's not even really an Ape program; just a list of symbols being pushed through as literals. But we can refactor a bit into [forward back line] foo let foo right left left foo which does the same thing. Prelude Before we really get started, let's build up a little bit of "library" support. We have yet to add numbers to Ape, but we'll need a way of at least repeating operations some number of times: [f let f f] 2 let Silly but it works! Almost like a "tally mark" counting system, each of these will take a quoted expression and apply it n times. I didn't want to bake in arbitrary movement and turning increments, but it is convenient to at least define some (but within Ape, not within the engine): [forward back 4 stop] F let Then from these define some common angles: [[R] 8] R90 let Interestingly, a series of "left right left right ..." will converge at 60 degrees, so an approximation: [[right left] 16] R60 let And we can compose these to get other angles: [R90 L60] R30 let Shape Up!Now that we have a bit of a foundation, it starts getting fun! [[[F] 8 line R90] 4] square let We can compose these: [R90 square L90 L30 triangle] house let We can start doing some powerful things; define procedures taking shapes and do things with them. Here, we draw a shape 16 times while rotating a bit each time: [shape let [shape [R] 2] 16] pattern let The results are very cool: [square] pattern It's just so fun to play with! [[arc R90] 2] peddle let Now remember that this is just 30 lines of code for the graphics rendering engine, 50 lines for the Ape interpreter, and another 30 lines for the lexer and parser. Pretty darn cool! 4月24日 Birth of a New Toy Language
I read “Thinking Fourth” and also played with Joy and Cat some time ago, but honestly I had written them off as “toy” languages. Recently I’ve had renewed interest in concatenative languages after watching Slava Pestov’s nice TechTalk about Factor. This is a very powerful implementation! Variation I decided to play with my own variation. The one thing that these stack-based languages seem to have in common is a soup of stack manipulation primitives and a lack of scoped name binding. They have a top-level “define” as their single means of abstraction. I understand the whole point-free style of concatenative languages in general, in which parameters are not named. I like this style actually because it is so very much more succinct! However, I don’t like the lack of scoped “defines” and I think that optionally popping and naming things on the stack can lead to clearer code in some cases. Also, I found that some of the other stack manipulation primitives were not really so “primitive.” If I had “let”, I could implement them in terms of it. Barebones What I really set out to do was to see what the minimal set of primitives should be. I found it annoying that Joy and Cat had all these stack manipulation primitives such as “dip”, “dup”, “pop”, “swap”, etc. My little moment of eureka was when I realized I could add a “let” primitive and get my scoped binding as well as be able to get rid of these other primitives! The language and data structure revolves around an extremely simplified AST structure: type node = Symbol of string | List of node list We have atomic Symbols and composite Lists (of Symbols and/or other Lists). That’s it! There is no native concept of Integers, Booleans, etc. Only Symbols. Further, there are only four primitive operations in my new language! cons/uncons We need a means of composing and decomposing structures. This is done through cons (adding a given node to a List) and uncons (breaking a given List into it’s “head” node and “tail” remaining List). For example: a [] cons yields [a] Decomposition yields the opposite: [a] uncons yields a [] eq For Symbols to be of any use at all, we need at least one operation we can perform on them. The single operation in the language is eq which compares two Symbols (or Lists) and evaluates one or another expression as a result. It takes four arguments, compares the first two and evaluates the third or fourth. For example: foo foo [yes] [no] eq yields yes The equality comparison walks the complete structure in the case of Lists. For example: [foo bar [baz]] [foo bar [baz]] [yes] [no] eq yields yes let The final missing piece is a means of abstraction. This is the most fundamental part of any useful language. We need to be able to introduce new “words” to the language and then use them as if they were primitives. For this, we have let. Let can be used to define new primitives by assigning a name to a List. For example, we could define a new “triple cons” operation such as: [cons cons cons] tcons let And now can, for example, use our new “word” to cons three Symbols onto an empty list: a b c [] tcons yields [a b c] Neat! We do have to have slightly special semantics for let. It does not apply to the top stack node as most “words” do. To ensure that Symbols used as identifiers are not evaluated, we do a single ‘look ahead’ while interpreting the code specifically require that the pattern a let means “bind the top stack node to the Symbol a.” Crazy-tiny Interpreter The interpreter is ridiculously small; less than 50 lines of code! let interpret input ast = let rec interpret' ast stack env = match ast with | Symbol(n) :: Symbol("let") :: tl -> match stack with | x :: s -> interpret' tl s (Map.add n x env) | _ -> failwith "Invalid stack state for 'let'." | Symbol(x) :: tl -> match Map.tryfind x env with | Some(List(f)) -> let r = match f with | [Symbol(y)] -> if y = x then (Symbol(y) :: stack) else interpret' f stack env | _ -> interpret' f stack env interpret' tl r env | Some(m) -> interpret' tl (m :: stack) env | None -> match x with | "cons" -> match stack with | List(t) :: h :: s -> interpret' tl (List(h :: t) :: s) env | _ -> failwith "Invalid stack state for 'cons'." | "uncons" -> match stack with | List(h :: t) :: s -> interpret' tl (h :: List(t) :: s) env | _ -> failwith "Invalid stack state for 'uncons'" | "let" -> failwith "Malformed 'let'." | "eq" -> match stack with | n :: y :: a :: b :: s -> let yn = if a = b then y else n match yn with | List(yn) -> let r = interpret' yn s env interpret' tl r env | _ -> interpret' tl (yn :: s) env | _ -> failwith "Not enough args on the stack!" | _ -> interpret' tl (Symbol(x) :: stack) env | x :: tl -> interpret' tl (x :: stack) env | [] -> stack interpret' ast input Map.empty Of course, if you want to use it you’ll also want a lexer and parser to convert source code to a node tree: type token = WhiteSpace | CombinationStart | CombinationEnd | Word of string
let lexer source = let rec lexer' source token result = let emit t tail = if List.length token > 0 then lexer' tail [] (t :: Word(new string(List.to_array (List.rev token))) :: result) else lexer' tail [] (t :: result) match source with | w :: t when (Char.IsWhiteSpace(w)) -> emit WhiteSpace t | '[' :: t -> emit CombinationEnd t | ']' :: t -> emit CombinationStart t | h :: t -> lexer' t (h :: token) result | [] -> result lexer' ((List.of_seq source) @ [' ']) [] []
let parser tokens = let rec parser' tokens result = match tokens with | Word(s) :: t -> symbolCount := symbolCount.Value + 1 parser' t (Symbol(s) :: result) | CombinationStart :: t -> listCount := listCount.Value + 1 let n, t = parser' t [] parser' t (n :: result) | CombinationEnd :: t -> List(result), t | WhiteSpace :: t -> parser' t result | [] -> List(result), [] let result, _ = parser' tokens [] result And you may want a way of “pretty printing” the program output: let output ast = let rec output' ast result = match ast with | Symbol(s) :: tl -> output' tl (result + " " + s) | List(l) :: tl -> output' tl (result + " [" + (output' l "") + " ]") | [] -> result output' ast "" First “Joy”ful Steps Now back to the so-called primitives who’s existence I found annoying in Joy and Cat. These can now be implemented in terms of the core four (cons, uncons, eq, let): [x let] pop let // throw away top stack node
I like this much better than adding them to the core language! You may have also noticed that the standard Lisp-esque car and cdr (or Haskell-esque head and tail) are missing from the language. They can be defined in terms of uncons: [uncons swap pop] head let [uncons pop] tail let Boolean Logic There is no Boolean type in the language. There is not even an if word! Let’s implement them! Using the symbols #t and #f to mean True and False (in Scheme-esque fashion), we can implement if in terms of eq: [[#t swap] dip eq] if let And we can start implementing the standard Boolean logic operators (not, and, or, xor) in terms of this: [#f #t if] not? let [[] [pop #f] if] and? let [[pop #t] [#t #t #f eq] if] or? let [#t [not?] [] eq] xor? let Also useful will be to create function that check equality to well known values and return #t or #f for use with other Boolean operators: [#t #f eq] equal? let [[] equal?] empty? let [0 equal?] zero? let [] true? let // wow, easy! [not?] false? Let What now? Well, now I’m going to add higher-order functions such as map, filter, fold, … and then add some basic arithmetic (using lists of decimal digits to represent numbers). Wow, a language with just Symbols and Lists for structure, just four operators and a 50-line interpreter. How fun! Is it just a “toy”? I’ll see how far I can take it… lots of ideas! 4月22日 Hello, my name is Ashley and I’m a perfectionist…
I must admit that I’m a recovering perfectionist; in everything really but particularly when it comes to code. I think that spending too much time with Donald Knuth’s seminal The Art of Computer Programming is to blame. Despite the name, his approach is much more science than art and left me with the impression that, with enough careful thought and design, a perfect implementation is attainable. I get a kick out of his version numbering scheme for TeX and METAFONT; being quite possibly the only bug-free, non-trivial system ever written. Instead of incremental version numbers like 1, 2, 2.5, 3, etc. he has added a digit with each update so that they approach perfect transcendental numbers. Only a programming god like Knuth could attempt perfection like this. He says,
Being a mere mortal dev myself, I’ve found it frustrating to even shoot for perfection. Plus I just plain find it much easier to iterate on an idea with working code than to plan everything out and expect to get it right the first time. Only my most trivial projects are anywhere near perfect. Scrum, TDD, iterative development is so much more natural. Software isn’t designed and then built like a physical building or a bridge. It’s an evolutionary process. Sometimes the ideas evolve in the one author’s head. Sometimes a codebase is more wiki-esque with a team of devs all free to get their fingers in each other’s code. Google, for example, actually has a single source tree shared company wide! Developers love to generalize. A lot of us love to build platforms rather than applications. Even when we’re customers of our own platform we build it in this stratified way because we love to solve the most general problem and then apply this to specific instances; even if the single instance is the only real task we’ve been given. I think that language oriented design is one of the most elegant ways to achieve this. You write a little language to solve a class of problems and then use that language to solve a particular instance. When first learning OO, I loved this idea of molding the language to fit the problem rather than restating the problem to fit the language. It’s a step in the right direction but really OO is only modeling the type system, not the whole of the language. I love the idea of constantly iterating on a solution, constantly trading between building up the platform and building up the application. Most of the time, I first end up implementing the naïve solution at the app level and then throwing it away. It’s not that it’s a mistake; it’s part of the process. I can be in no position to start building the platform piece until I’ve had some experience with the problem at the app level. The app drives the platform. That means though that there’s a constant cycle of building, scraping, generalizing, and reimplementing. Eventually the tension between app and platform settles. You’re left with something that’s not necessarily perfect but is tending towards perfection and has reached a kind of natural boundary where it begins to feel artificial to generalize further and pull more code into the platform. It’s a natural progression and knowing when to stop is usually easy; you’ve hit an inherent barrier. Paul Graham said it very nicely in his book “On Lisp”:
It’s not meant for you to know up front where the lines should be drawn, and as requirements change and new applications use the platform (but mainly as your understanding of the problem deepens) the lines constantly change. It’s not a bad thing that the initial design was flawed. That should be wholly expected. So does this leave you with an uneasy feeling? 4月3日 CryptarithmA recent post to the Puzzles and Logic Problems alias at work.
The problem below is an example of a cryptarithm – a basic math problem made more difficult by obscuring each digit with a letter or other symbol.
In this problem, there are 10 unique letters. As an initial hint, S = 1. Each letter represents a unique integer ranging from 0-9, so given the hint that S = 1, no other letter is equal to ‘1’.
With some stolen code for the permutations function, my solution turned out pretty succinct in F#:
let let let validate attempt = let print = Seq.iter2 (printfn "%c = %i") "SABRELOMHV" let attempts = permutations [0..9] (Set.singleton s) 7月30日 The Feniello KeyboardSome years ago (while suffering a little RSI) I learned to touch type on a Dvorak keyboard; difficult to break old habits, but much, much more comfortable feeling. Seriously, I would recommend it! Then a year or so ago I was very bored one day and decided to invent my own keyboard (yes, I am that much of a dork). Beyond ETAOIN SHRDLU I ran a key logger on my dev machine for a couple of weeks and came up with key-pair-timings. For example, after pressing key ‘A’, how long does it take to press key ‘B’; and do that for every combination of keys. Here’s a heat map of exactly that: Darker red is slower. You can see that after pressing ‘A’ (with my pinky) it’s very slow to follow up with another pinky-key. Quickest by far is to follow up with an opposite-handed home-row key. Well of course! The results were pretty much as expected. For example, here’s after pressing ‘L’: Look at the L-K pair; very fast; I suspect because you can kind of roll your fingers. Just imagine if T-H-E (the most common English word) was on the L-K-J keys. You could type it almost as a single motion! Optimizing for “the” is not necessarily best though. Optimized For Me I optimized for the sequences of keys that I actually type. Lots of email. Lots of C# and F# code. Lots of technical documentation. I used a simulated annealing type algorithm to come up with a keyboard layout that was optimized for time; given key timings and sequences based on my personal actual usage over two weeks. Here’s the result: The punctuation keys aren’t placed. The algorithm was coming up with totally bizarre layouts that were just way to hard to use! Notice there are two ‘E’s! One above each middle finger. Genetic algorithms are so strange; coming up with things I would never have thought of! In practice though, I found it actually difficult to use the ‘correct’ E-key while typing; I’d instead favor one or the other regardless of the follow-up key. I’m Not Dork Enough… I don’t actually use the “Feniello” keyboard layout. Some day when I’m bored enough again… What I really use when writing code is: :-) 7月17日 Rubik's Cube SolverA while back, I had some fun making a Rubik's cube solver in F#. I was planning to make a "pluggable algorithm" mechanism so that I could have it solve using other human-style systems such as Petrus, Fridrich, etc., but right now it just uses a vanilla layer-by-layer system. I've lost interest in this hobby-project so now just posting as is:
The UI
The UI is a bit ugly (nice 3D cube would be good) but works. It's an "unfolded" projection of the cube faces:
You can flip the left, right, up, down, front, and back faces by pressing L, R, U, D, F or B keys respectively and you can rotate the whole cube on it's X, Y or Z axis with those keys or with the arrow keys (X/Y axis only). You can also mix it up with M and reset it with Spacebar.
For example, U R D L produces:
Or, from the reset position, U U D D L L R R F F B B produces the nice pattern:
The real purpose though was to make a solver. Each time you press S, it manipulates the cube a step closer to being solved. The interesting thing (to my anyway) is that the solving algorithm is a single function that takes a single argument of a cube in a particular state and returns a new cube that's been manipulated a bit. The process of solving is just to iterate this function. The only state in the system is the state of the cube being displayed; literally you will see in the code that there is a single cube ref and no other mutables at all.
The Code
The Cube is represented as a 3x3x3 array of Cublets, each of which have an up/down/left/right/front/back Sticker:
#light
open System.Drawing
open System.Windows.Forms
type Sticker = Red | Orange | Yellow | Green | Blue | White
Cublets just have the six stickers and methods for manipulating their orientation:
// The naming conventions for all the transforms (e.g X,
// X2, X', Y, Z, F, U, D, D', L2, etc.)
// come from http://www.cubewhiz.com/notation.html
type Cublet = class
val up : Sticker; val down : Sticker
val left : Sticker; val right : Sticker
val front : Sticker; val back : Sticker
new(u, d, l, r, f, b) = { up = u; down = d; left = l; right = r; front = f; back = b }
new() = { up = Sticker.Red; down = Sticker.Orange;
left = Sticker.Yellow; right = Sticker.White;
front = Sticker.Blue; back = Sticker.Green }
member c.X = new Cublet(c.front, c.back, c.left, c.right, c.down, c.up) // back on X-axis
member c.X2 = c.X.X // 180 on X-axis
member c.X' = c.X2.X // inverse - forward on X-axis (same as back 3)
member c.Y = new Cublet(c.up, c.down, c.front, c.back, c.right, c.left) // left on Y-axis
member c.Y2 = c.Y.Y // 180 on Y-axis
member c.Y' = c.Y2.Y // inverse - right on Y-axis (same as left 3)
member c.Z = c.X.Y.X' // clockwise on Z-axis (same as X, Y, X')
member c.Z2 = c.Z.Z // 180 on Z-axis
member c.Z' = c.Z2.Z // inverse - counter clockwise on Z-axis (same as clockwise 3)
end
Finally, the Cube is a 3x3x3 array of Cublets with methods for manipulating sets of them (rotating slices or changing orientation of the whole cube):
let s = 3 // Cube dimentions
let a = Array3.init s s s
type Cube = class
val cublets : Cublet [,,]
new(c) = { cublets = c }
new() = { cublets = a (fun _ _ _ -> new Cublet()) }
member c.X = new Cube(a (fun x y z -> c.cublets.[x, s - z - 1, y].X)) // back on X-axis
member c.X2 = c.X.X // 180 on X-axis
member c.X' = c.X.X.X // inverse - forward on X-axis (same as back 3)
member c.Y = new Cube(a (fun x y z -> c.cublets.[s - z - 1, y, x].Y)) // left on Y-axis
member c.Y2 = c.Y.Y // 180 on Y-axis
member c.Y' = c.Y2.Y // inverse - right on Y-axis (same as left 3)
member c.Z = c.X.Y.X' // clockwise on Z-axis (same as X, Y, X')
member c.Z2 = c.Z.Z // 180 on Z-axis
member c.Z' = c.Z2.Z // inverse - counter clockwise on Z-axis (same as clockwise 3)
member c.F = new Cube(a (fun x y z -> if z = 0 then c.cublets.[y, s - x - 1, z].Z else c.cublets.[x, y, z]) )
member c.F2 = c.F.F
member c.F' = c.F2.F
member c.B = c.Y2.F.Y2 // everything defined in terms of F, X and Y!
member c.B2 = c.B.B
member c.B' = c.B2.B
member c.L = c.Y'.F.Y
member c.L2 = c.L.L
member c.L' = c.L2.L
member c.R = c.Y.F.Y'
member c.R2 = c.R.R
member c.R' = c.R2.R
member c.U = c.X'.F.X
member c.U2 = c.U.U
member c.U' = c.U2.U
member c.D = c.X.F.X'
member c.D2 = c.D.D
member c.D' = c.D2.D
end
The solver is a giant beast! The cool thing about it is that you just give it a Cube (in any state) and it gives you one back, having done some manipulations. It reports to the console what it's "thinking" as it goes.
Here's a sample solve (showing completion of each phase; it actually took ~80 steps pressing 'S' key):
The code is one giant "solve" function:
[Dang it! Silly MSN Spaces won't let me paste so much in with color highlighting...
let solve (c : Cube) =
let furc = c.cublets.[2, 0, 0].front let ufrc = c.cublets.[2, 0, 0].up let rufc = c.cublets.[2, 0, 0].right let fdrc = c.cublets.[2, 2, 0].front let dfrc = c.cublets.[2, 2, 0].down let rdfc = c.cublets.[2, 2, 0].right let dfe = c.cublets.[1, 2, 0].down let fde = c.cublets.[1, 2, 0].front let dbe = c.cublets.[1, 2, 2].down let dre = c.cublets.[2, 2, 1].down let bde = c.cublets.[1, 2, 2].back let dle = c.cublets.[0, 2, 1].down let ule = c.cublets.[0, 0, 1].up let ure = c.cublets.[2, 0, 1].up let ufe = c.cublets.[1, 0, 0].up let ube = c.cublets.[1, 0, 2].up let ublc = c.cublets.[0, 0, 2].up let ubrc = c.cublets.[2, 0, 2].up let uflc = c.cublets.[0, 0, 0].up let ufrc = c.cublets.[2, 0, 0].up let bulc = c.cublets.[0, 0, 2].back let burc = c.cublets.[2, 0, 2].back let lubc = c.cublets.[0, 0, 2].left let lufc = c.cublets.[0, 0, 0].left let fulc = c.cublets.[0, 0, 0].front let furc = c.cublets.[2, 0, 0].front let rubc = c.cublets.[2, 0, 2].right let rufc = c.cublets.[2, 0, 0].right let fue = c.cublets.[1, 0, 0].front let lue = c.cublets.[0, 0, 1].left let rue = c.cublets.[2, 0, 1].right let bue = c.cublets.[1, 0, 2].back let fs = c.cublets.[1, 1, 0].front let bs = c.cublets.[1, 1, 2].back let ls = c.cublets.[0, 1, 1].left let rs = c.cublets.[2, 1, 1].right let w = Sticker.White let y = Sticker.Yellow let fre_done (c : Cube) = c.cublets.[2, 1, 0].front = c.cublets.[1, 1, 0].front && c.cublets.[2, 1, 0].right = c.cublets.[2, 1, 1].right printfn "------------------------------------------------------------" match c.cublets.[1, 0, 1].up (* up center *), c.cublets.[1, 1, 0].front (*front center *) with | Sticker.Yellow, _ -> // top already yellow printfn "Phase 0: Top is yellow." if // white cross on down-side complete? dfe = w && fde = fs && dbe = w && bde = bs && dle = w && bde = bs && dre = w then printfn "Phase 1: White cross is complete."; let frdc_done (c : Cube) = c.cublets.[2, 2, 0].down = w && c.cublets.[2, 2, 0].front = c.cublets.[1, 1, 0].front && c.cublets.[2, 2, 0].right = c.cublets.[2, 1, 1].right if frdc_done c && frdc_done c.Y && frdc_done c.Y2 && frdc_done c.Y' // corners complete? then printfn "Phase 2: Bottom layer corners are complete."; if fre_done c && fre_done c.Y && fre_done c.Y2 && fre_done c.Y' then printfn "Phase 3: Middle layer edges in place." if ule = y && ure = y && ufe = y && ube = y // yellow cross? then printfn "Phase 4: Yellow cross on top complete." let n = Array.sumByInt (fun c -> if c = y then 1 else 0) [| uflc; ufrc; ublc; ubrc |] if n = 4 then printfn "Phase 5: Yellow top face complete." let bp = bulc = burc let lp = lubc = lufc let fp = fulc = furc let rp = rubc = rufc if bp && lp && fp && rp then printfn "Phase 6: Top corners aligned." if lue = ls && bue = bs && rue = rs && fue = fs // solved?! then printfn "Phase 7: SOLVED!"; c elif lue = lubc then printfn " Move finished edge from left to the back."; c.U elif rue = rubc then printfn " Move finished edge from right to the back."; c.U' elif fue = fulc then printfn " Move finished edge from front to the back."; c.U2 elif fue = rubc then printfn " Move front edge (right) into place."; c.F2.U'.L.R'.F2.L'.R.U'.F2.U2 elif fue = lubc then printfn " Move front edge (left) into place."; c.F2.U.L.R'.F2.L'.R.U.F2.U2 else c elif lp then printfn " Move paired corners from left to the back."; c.Y elif rp then printfn " Move paired corners from right to the back."; c.Y' elif lp then printfn " Move paired corners from front to the back."; c.Y2 else printfn " Permutate top corners."; c.R'.F.R'.B2.R.F'.R'.B2.R2 else // get top yellow face if if n = 0 then printfn " No yellow corners on top." c.cublets.[0, 0, 0].left = y elif n = 1 then printfn " 1 yellow corners on top." c.cublets.[0, 0, 0].up = y elif n = 2 then printfn " 2 yellow corners on top." c.cublets.[0, 0, 0].front = y else failwith "Impossible position!" then printfn " Permutate yellow corders."; c.R.U.R'.U.R.U2.R' else printfn " Rotate yellow corner into position according to corner count."; c.U // get yellow cross elif ule <> y && (ure = y || ufe = y || ube = y) then printfn " Line up top yellow pattern."; c.U else printfn " Permutate yellow edges."; c.F.U.R.U'.R'.F'.U else // get middle layer edges in place if fre_done c then printfn " Middle layer edge in place. Next..."; c.Y elif c.cublets.[2, 1, 0].front = rs && c.cublets.[2, 1, 0].right = fs then printfn " Middle layer edge in place, but flipped wrong."; c.R.U.R'.U'.F'.U'.F.U'.R.U.R'.U'.F'.U'.F elif c.cublets.[2, 0, 0].front = fs && c.cublets.[2, 0, 0].up <> y then printfn " Middle layer edge just nearly in place."; c.U // it will go in next most else printfn " Permutate top and right front edges."; c.R.U.R'.U'.F'.U'.F.U.Y else // get bottom corners in if // corner in place? dfrc = w && fdrc = fs && rdfc = rs then printfn " Down-right corner already in place. Rotate view."; c.Y elif // corner at least lined up vertically? ( // furc lined up? (furc = w || ufrc = w || rufc = w) && // white? (furc = fs || ufrc = fs || rufc = fs) && (furc = rs || ufrc = rs || rufc = rs) // correct corner? ) || ( // fdrc lined up? (fdrc = w || dfrc = w || rdfc = w) && // white? (fdrc = fs || dfrc = fs || rdfc = fs) && (fdrc = rs || dfrc = rs || rdfc = rs) // correct corner? ) then printfn " Slip white corner into place."; c.R.U.R'.U' else printfn " Safely permutate corners."; c.U2.R.U.R'.U'.Y // line up white edges to make cross on down-side elif // white edge lined up with correct right-center? c.cublets.[2, 1, 0].front (* front right edge *) = w && c.cublets.[2, 1, 0].right (* right front edge *) = rs then printfn " Rotate right white edge in."; c.R' // rotate white edge in elif // white edge lined up with correct right-center, but flipped wrong way? c.cublets.[2, 1, 0].front (* front right edge *) = rs && c.cublets.[2, 1, 0].right (* right front edge *) = w then printfn " Rotate flipped right white edge in."; c.D'.F.D // rotate white edge in elif // white edge lined up with correct down-center, but flipped wrong way? c.cublets.[2, 2, 1].right (* right down edge *) = w && c.cublets.[2, 2, 1].down (* down right edge *) = rs then printfn " Rotate flipped down white edge in."; c.R.D'.F.D // rotate white edge in elif // white edge on down side, but wrong one? c.cublets.[2, 2, 1].down (* right down edge *) = w && c.cublets.[2, 2, 1].right (* down right edge *) <> rs // wrong color? then printfn " Rotate wrong down white edge to top."; c.R2.U // rotate white edge out else printfn " Safely permutate edges."; c.R.U.R'.U'.Y // just safely "stir thing up" and continue // look at two faces and flip yellow to up | _, Sticker.Yellow -> printfn " Flip yellow side from front to up."; c.X // front is yellow | _, Sticker.White -> printfn " Flip yellow side from back to up."; c.X' // back must be yellow | Sticker.White, _ -> printfn " Flip yellow side from down to up."; c.X2 // down must be yellow | Sticker.Red, Sticker.Blue | Sticker.Blue, Sticker.Orange | Sticker.Orange, Sticker.Green | Sticker.Green, Sticker.Red -> printfn " Flip yellow side from left to up."; c.Z // left must be yellow | Sticker.Blue, Sticker.Red | Sticker.Red, Sticker.Green | Sticker.Green, Sticker.Orange | Sticker.Orange, Sticker.Blue -> printfn " Flip yellow side from right to up."; c.Z' // right must be yellow | _ -> failwith "Impossible position!" So, finally the UI is very simple; just painting and key wireup:
Looks like a lot of work but really less than 300 lines of code; Fun little toy project! 5月9日 Popfly Game CreatorAnd I thought Scratch was cool. Dang, Popfly Game Creator rocks! Be sure to watch the video... 5月2日 Closures to the RescueA coworker just walked into my office with an interesting refactoring question; hoping an FP-style would help. I think it’s a decent example of how mutation generally gets in the way of compositionality, but also of how FP can still be applied in a mixed world like this; a good enough example, I thought I’d blog about it. Basically he has a standard “hole in the middle” problem for which FP is normally perfect, but in his case the code is written in an imperative style and so the “holes” have to share state. Essentially, he has some retry logic that he wants to reuse. The “holes” are to A) initialize some state, B) make multiple attempts to do something that may fail (e.g. network requests, etc.), and C) finish by cleaning up and generating a result of some kind. Example First we need something dangerous to try (and retry upon failure). For simplicity, I’ll just use a silly DiceRoll() function that blows up 1/7th of the time: private static int DiceRoll() Then, a super-simplified version of some kind of retry logic; in this case returning the sqrt of 2 + a random dice roll (real useful stuff). It’ll give it three chances before giving up: // A) init (mutation!) We’d like to reuse this logic; plugging in new implementations of the A, B and C “holes”. How do we do that? From an FP mindset, the first thing that comes to mind is to bundle it up as a function taking three higher-order functions (A, B and C) as arguments. The Problem In a pure functional world, that would be perfectly easy and you’d be free to compose any arbitrary (independent) implementations of A, B and C. The main problem here is that B depends on the state left behind by A (in this case, the value of ‘x’), and C depends on the state changes made by B; they’re not independent. Shared state makes it messy (this is why FPers avoid it). By definition this gets in the way of composing “mis-matched” A/B/C sets, but it shouldn’t get in the way of generally passing in higher-order functions to factor out the reusable retry logic. We just need some way to pass around separate functions that share state. OO Solution The OO programmers first thought is to not make them entirely separate functions, but to make them separate methods of one object containing the shared state. We’d start out with an interface or abstract class: public interface IAttemptDangerousThings Then we’d implement the “holes”: public class AttemptMe : IAttemptDangerousThings Finally, we’d make a function that bundles up the retry logic and takes instances of this interface type (or make it a member, but then it’s harder to mix and match with different ‘retry’ implementations). public static void OOSolution(IAttemptDangerousThings doSomething) We get the compositionality we wanted. We can even use inheritance to compose various versions of A, B and C (in limited ways). FP Solution The idea of refactoring the way we have so far is already an FP-type idea, but the main reason I dislike the OO “packaging” of higher-order functions with shared state is just that it’s verbose; having to construct whole class hierarchies just for “packaging” and also I think inheritance is a clunky way of allowing mixing and matching A’s, B’s, and C’s. I’d much rather just use a delegate: public delegate void DoSomething();
And change the retry logic to take three of these guys: public static void FPSolution( Then it’s very simple and lightweight to throw together the pieces: int y = 0; // what the?! delegates can access this? Closures! What might look strange about the three anonymous delegates above is that they all refer to ‘y’, which is defined outside of their scope! If you haven’t seen this before you might be wondering if that really compiles, and what happens when the locals go out of scope but the delegates still exist on the heap. If you’ve always thought of delegates as just type safe function pointers, they’re more than that. They are ‘closures’ that can ‘capture’ the environment in which they’re first created. They can even be returned out of their original scope and continue to capture out-of-scope variable (such as ‘y’). It’s pretty cool stuff! In fact, who even needs OO? Really, “objects” with private state and methods that share it can easily be simulated with closures. Plenty of OO systems with inheritance, polymorphism and the whole shebang have been built in Scheme this way. It’s an age-old thing in FP languages, but new in C# since anonymous delegates were introduced. Anyway, now we can just pass these delegates/closures into our FPSolution(init, attempt, finish); and voila! 4月18日 Last Monkey StandingIt's a sad day around here. The Barrel of Monkeys have been completely strewn about the company. Gram and I were the last holdouts and now he's leaving. It's been great working with you man! You're absolutely the best person I've ever worked with. 12月3日 The Lambda CalculusMads Torgersen's post the other day was very cool! The first time I really understood Lambda Calculus was from this little 8-page paper - for some reason, relating it to Scheme made it stick for me. After reading Dr. T's post I was just now having some fun playing with it in Scheme and couldn't resist making a post of my own To start off, we can define true, false and if: (define (true a b) a) Each of 'true' and 'false' are functions taking a pair of arguments and return one or the other of them. Then 'if' simply takes a boolean (instance of our true/false function) and applies it to a pair. Very strange to think of 'true' and 'false' as functions but everything is a function in Lambda Calculus. We can test it out with: > (if true 1 2) Works! This is very similar to how 'cons', 'car' and 'cdr' are defined: (define (cons a b) (lambda (c) (if c a b))) Calling 'cons' creates a pair in the form of a closure that when passed 'true' returns the head and when passed 'false' returned the tail. Again, it's strange to think of a pair not as a data structure but as a function - everything is a function in LC. Seeing this was one of the awe moments for me when when going through SICP. So, the 'car', just takes a pair and calls it with 'true'. Likewise 'cdr' calls with 'false'. Simple as that. We can test it: > (define foo (cons 1 2)) For demonstration, we're using the Scheme number literals '1' and '2' but these don't exist in LC! Instead, how do you get numbers? Did I mention that everything is a function in LC?! Yes, these too are functions: (define (zero f) (lambda (x) x)) What the heck?! In Church encoded numerals, 'one' is a function that takes a function as an argument and applies it once. Similarly, 'two' takes a function and applies it twice ("f of f of x"), 'three' applies three times, etc. Does your brain hurt yet? That's all part of the fun! Once we start working with Church Numerals, we'll need a way to visualize them. Here's a helper to convert Church Numerals to normal Scheme Numerals: (define (print n) ((n (lambda (n) (+ n 1))) 0)) It uses the fact that Church Numerals are functions that apply functions n times. (print n) will apply n to a simple "increment" function, producing a result we can see printed for human consumption. It uses the '+' operator and Scheme literals '0' and '1' which aren't part of LC - just for demo purposes. So now we can do things like: > (print one) Another useful function for dealing with Church numerals is to be able to recognize zero (from this we could build greater-than/less-than/equal-to operators): (define (zero? n) ((n (lambda (x) false)) true)) This 'zero?' function works by the fact that the Church numeral 'zero' applies its argument (a function) zero times, so we pass a function that, if called at all, returns false. Funny, eh? We can try it out (along with our 'if'): > (if (zero? three) "yes" "no") It's a pain to define each of our 'one', 'two', 'three' literals. We can generalize the process with a 'succ' (successor) function: (define (succ n) (lambda (f) (lambda (x) (f ((n f) x))))) ... and then redefine our natural number literals in terms each other - ultimately in terms of just 'zero' (like Peano's 5th and 6th postulates): (define one (succ zero)) Cool, now we can even generate numbers for which we have no literal defined: > (print (succ ten)) Defining 'pred' (predecessor) is a little tricky: (define (pred n) (cdr ((n (lambda (p) (cons (succ (car p)) (car p)))) (cons zero zero)))) We define a function that generates pairs of numbers (using our 'cons') which represent the next (using our 'succ') and the previous number. Given a pair, it will generate the next pair. So we can call it n times starting with (cons zero zero) and then take the 'cdr' and voila, we have the number prior to n. Of course, we call it n times by passing it to n (working with Church numerals is so weird!). It all works: > (print (pred seven)) Using 'pred' we can define 'sub' (subtract) and we can define 'add' in terms of 'succ'. Then we can define 'mult' in terms of 'add'. In fact, there's nothing in mathematics that cannot be defined ultimately in LC! (define (sub a b) ((b pred) a)) The most insane thing of all is when you get to the Y Combinator which finds the fixed point of other functions so that they can "generate themselves" to do recursion. Below is the Z Combinator (just like Y but made for applicative order languages like Scheme - where just the definition of Y would cause infinite evaluation!). I'm not going to try to explain it (the two links at the top of this post do a better job than I ever could): (define Z (lambda (f) ((lambda (x) (f (lambda (y) ((x x) y)))) (lambda (x) (f (lambda (y) ((x x) y))))))) Now, for the grand finale. We can define good ol' factorial as a function that takes an instance of itself (here 'f') and does the normal, "if n is zero then return 1, otherwise return n * factorial of n - 1" only when it says "factorial of ..." it means "apply the instance of ourselves that was passed in". Very strange stuff, but this essentially defines 'fac' in terms of itself without itself referring to the name 'fac' anywhere in it's body: (define fac (Z (lambda (f) (lambda (n) (if (zero? n) one (mult n (f (sub n one)))))))) And finally, we can test this out: > (print (fac seven)) Amazing! If we really want to get crazy we can go through and curry all the functions we've defined. For example, 'add' could be redefined as: (define add (lambda (a) (lambda (b) ((b succ) a)))) In other words, instead of taking two arguments ('a' and 'b' as it was defined earlier), 'add' is now a lambda that takes just one ('a') and returns a new lambda that takes the second argument ('b') and does the calculation. Any function taking multiple arguments can be reduced to a chain of functions like this; each taking just one argument. This was one of the big contributions of Haskell Curry. It's more of a pain to call this new definition of 'add' but it works: > (print ((add three) four)) Furthermore, if we want to go completely out of our minds, because of referential transparency we can go through our definition of 'fac' and replace all the references to various things we've defined ('sub', 'one', 'mult', 'if', etc.) with the bodies of those definitions (and in turn, replace any references in those bodies. For example, replacing "add" with "(lambda (a) (lambda (b) ((b succ) a)))" and inside that replacing 'succ' it's definition, etc. - we can do this blindly without breaking anything. And since 'fac' doesn't even refer to it's own name (due to the beautiful Y/Z Combinator) we can systematically reduce our 'fac' to only a bunch of functions calling functions and returning functions. Here's the result: (define fac ((lambda (f) ((lambda (x) (f (lambda (y) ((x x) y)))) (lambda (x) (f (lambda (y) ((x x) y)))))) (lambda (f) (lambda (n) ((((lambda (c) (lambda (a) (lambda (b) ((c a) b)))) ((lambda (n) ((n (lambda (x) (lambda (a) (lambda (b) b)))) (lambda (a) (lambda (b) a)))) n)) (lambda (f) (lambda (x) (f x)))) (((lambda (a) (lambda (b) ((a (lambda (x) (((lambda (a) (lambda (b) ((b (lambda (n) (lambda (f) (lambda (x) (f ((n f) x)))))) a))) x) b))) (lambda (f) (lambda (x) x))))) n) (f (((lambda (a) (lambda (b) ((b (lambda (n) ((lambda (c) (c (lambda (a) (lambda (b) b)))) ((n (lambda (p) (((lambda (a) (lambda (b) (lambda (c) ((((lambda (c) (lambda (a) (lambda (b) ((c a) b)))) c) a) b)))) ((lambda (n) (lambda (f) (lambda (x) (f ((n f) x))))) ((lambda (c) (c (lambda (a) (lambda (b) a)))) p))) ((lambda (c) (c (lambda (a) (lambda (b) a)))) p)))) (((lambda (a) (lambda (b) (lambda (c) ((((lambda (c) (lambda (a) (lambda (b) ((c a) b)))) c) a) b)))) (lambda (f) (lambda (x) x))) (lambda (f) (lambda (x) x))))))) a))) n) (lambda (f) (lambda (x) (f x))))))))))) > (print (fac seven)) Notice that all we need is 'lambda' - it's the ultimate all right! Totally crazy! But it proves that once we boil everything away, what we're left with is the very essence of computation: only functions taking a single function as an argument and returning a function (of the same). That's The Lambda Calculus. 11月13日 SICP, Take 2I absolutely loved reading SICP and listening to the lectures! This UC Berkeley course is proving to be a second wave of enlightenment. Such valuable material, and all free! 11月6日 Chasing the TailRecursion is absolutely ubiquitous as the loop control flow mechanism in a world without assignment. Familiar constructs such as for/while loops make no sense without mutation.
Here is a recursive definition of a recursive process for calculating factorial (in Scheme):
(define (factorial n) (if (<= n 1) 1 (* n (factorial (- n 1))) ) )
My previous post has a nice example of how the “shape” of the process can be seen by way of successive substitutions.
This now, is another recursive definition of factorial, but this time defining an iterative process using the common tactic of an accumulator:
(define (factorial n a) (if (<= n 1) a (factorial (- n 1) (* n a)) ) )
Comparing these in the “Stepper” in DrScheme makes for a nice example:
The former builds up deferred multiplications while the latter accumulates the answer directly.
So what happens when we try this in C#?
using System;
public static class Tail {
public static void Main() {
Watching in the debugger, you can see that a call stack is built and then collapsed even though this is clearly tail recursive.
Looking at the IL for this we can see that it’s doing a regular “call” and “ret” – silly C# compiler!
Tail recursion can be thought of as a “goto with parameters” and we can manually implement this if we like:
public static int Factorial(int n, int a) {
But how horribly ugly! Using mutation and goto! Notice also that if a *= n; and n--; were reversed it would be a bug. As soon as you introduce assignment, bugs like this are possible.
Side note: I enjoy being one of the happy devs who’s moved to managed languages and longer has to worry about stack and heap corruption, memory leaks and buffer overruns. Next I want to move away from the store and branch instructions in our von Neumann machines. We all broke free of goto many years ago but most are still fighting with assignment to this day. Eliminating this whole class of bugs will be great.
Another example that comes to mind: Some people find it amazing to hear that in F# you only very, very rarely have any need at all for the concept of null (mainly only for backward compat with .NET libraries). Wouldn't you love to say, "NullRefException? What's that!?”
Okay, back to recursion; lets try this again in F# this time:
let rec Factorial n a = Factorial 7 1
In the debugger, it’s apparently smart enough to eliminate the call stack! Excellent.
Let’s look at the IL:
Sweet, the compiler is doing the “sub”, “mul” and stomping on the original arguments (“starg.s”), then simply branching (“br.s”) back to the beginning. I have no problem with compiler-generated mutation and goto.
I’m curious now, if the compiler is smart enough to optimize tail recursion in the form of a function calling itself, what will it do with proper tail calls to other functions? It can’t be branching between functions:
let rec Even n = And Odd n = Even 5
Looking in the debugger, we can see that Even 5 calls Odd 4, which calls Even 3, calling Odd 2, which finally calls Even 1 and returns false; all of this without a call stack.
Nice. But how? As usual, the IL gives it away:
The “.tail” prefix tells the JIT compiler that stack frame can be recycled before the following “call.” It turns out that even without this prefix, the JIT compiler can potentially be smart enough to optimize. Jomo Fisher has a nice post on this.
I just gave a brownbag talk on this. Here's the deck if you're interested. 10月30日 The Great DebateI found this interview/debate with Mads Torgersen (C# guy), Joe Armstrong (Erlang guy) and Erik Meijer (Haskell guy) very entertaining!
It's a classic debate. I remember hearing it in the 25-year-old SICP lectures in which Hal Abelson said,
But hey, before you start thinking that Erlang must be the answer to all the world’s problems 10月24日 Map / Reduce / FilterI just did a little Functional Programming brownbag talk today. One example that went over pretty well was this:
This works to find the minimum price for gas within 5 miles; taking into account a 10% Safeway discount. Ahhh! Mutation and mixing of intent all over the place!
Let’s see if we can, most importantly, separate out the intentions and also get rid of the use of assignment:
Examining the code in red, you notice that it's essentially doing a map (just converting a collection of GapResults to adjusted doubles), and the green is a reduce operation (threading the min accumulator throughout the iteration) and the purple is obviously a filter. This can be rewritten in a much more straightforward way using the functional extension methods hanging off of IEnumerable in .NET 3.5+:
Of course there’s already a Min() method available and additionally it’s even more succinct using LINQ keywords:
Why would we ever want to write it in the "mini-Rube Goldberg machine" imperative style?
Here's the brownbag deck if you're interested... 9月21日 F# For FunMr. GeekRaver and I were walking between buildings and, for some reason, talking about how silly it is that good ol' Minesweeper was "glossed up" for Vista. He said he had written it for DOS in some tiny amount of C code (http://www.bradygirl.com/Work/mine.c). His explanation of little tricks to make the implementation simple (e.g. a traverse() function that walks the board applying a passed in function to each cell) sounded like FP to me. So, here it is in about 40 lines of F# (plus another 50 for UI). Now, my challenge to him is to beat that using Python (his current kick) this time... #light
#nowarn "57" // multiple additive results from sequence expressions
(*
This is a simple implementation of the Windows game Minesweeper.
Ideas were shamelessly stolen from my office-mate Gram's DOS version:
http://www.bradygirl.com/Work/mine.c
Funny enough, the coolest part of his C code is his functional style.
His traverse() function is essentialy a 'map' used to walk the board
applying a passed-in function (check_neighbors, etc.) and also
recursive_expose() itself is pretty cool. Let's try it in a proper
functional language:
*)
open System
open System.Drawing
open Windows.Forms
let w = 9 // width of mine field
let h = 9 // height
let d = 10 // 1:10 mine density
type State = Unknown | Flag | Expose
let randomField = // init field w/randomly placed mines
let r = new Random(int DateTime.Now.Ticks)
Array2.init w h (fun y x -> (r.Next(d) = 0), Unknown)
let adjacentCoordinates x y = [ // list of adjacent x,y pairs
for i in x - 1 .. x + 1
for j in y - 1 .. y + 1
when (i <> x || j <> y) && // not including x,y itself
i >= 0 && i < w && j >= 0 && j < h // not off edges
-> (i, j) ]
let adjacentCells m xy = // list of adjacent cells
List.map (fun xy -> m.[fst xy, snd xy]) xy
let countMines = Seq.sumByInt (function (true, _) -> 1 | _ -> 0) // given Seq of cells
let countFlags = Seq.sumByInt (function (_, Flag) -> 1 | _ -> 0)
let setCell f m x y = // apply f to snd of cell x,y of m
Array2.mapi (fun i j c -> if i = x && j = y then (fst c, f (snd c)) else c) m
let setFlag = // toggle Flag, but leave already Exposed as is
setCell (function Unknown -> Flag | Flag -> Unknown | Expose -> Expose)
let setExpose = setCell (function Flag -> Flag | _ -> Expose) // leaved Flagged as is
// this is the only interesting part :-)
// we automatically expose any cells known to be safe based on the exposed
// mine counts and surrounding flags (players incorrectly flagging cells may blow up!)
let rec autoExpose m xy =
match xy with
| [] -> m
| (x, y) :: t ->
if (snd m.[x, y]) = Unknown then
let n = setExpose m x y
let a = adjacentCoordinates x y
let c = adjacentCells n a
autoExpose n (if countFlags c >= countMines c then (a @ t) else t)
else autoExpose m t // already Exposed or Flagged
// that's it, the rest is UI:
let form =
let field = ref randomField
let s = 20 // grid size
let paintField (g : Graphics) =
let paintCell (g : Graphics) x y c =
let i = x * s // x-coordinate
let j = y * s // y-coordinate
let l = i + 1 // left (with 1 pix inset)
let t = j + 1 // top
let (r : int) = i + s - 1 // right
let b = j + s - 1 // bottom
let i = s - 1 // inner size
let f = new Font(FontFamily.GenericSansSerif, 10.0f, FontStyle.Bold)
match c with
| (_, Unknown) -> // "embossed"-looking highlights
g.DrawLine(Pens.DarkGray, r, t, r, b)
g.DrawLine(Pens.DarkGray, l, b, r, b)
g.DrawLine(Pens.White, l, t, r, t)
g.DrawLine(Pens.White, l, t, l, b)
| ( _, Flag) -> g.FillRectangle(Brushes.Green, l, t, i, i)
| (true, Expose) -> g.FillRectangle(Brushes.Red, l, t, i, i)
| ( _, Expose) ->
g.FillRectangle(Brushes.Gray, l, t, i, i)
let n = (countMines (adjacentCells !field (adjacentCoordinates x y))).ToString()
if n <> "0" then
let b = match n with
| "1" -> Brushes.LightBlue
| "2" -> Brushes.LightGreen
| "3" -> Brushes.LightCoral
| _ -> Brushes.White
let m = g.MeasureString(n, f)
g.DrawString(n, f, b,
float32 l + (float32 s - m.Width) / 2.0f,
float32 t + (float32 s - m.Height) / 2.0f)
g.Clear(Color.Gainsboro)
Array2.iteri (paintCell g) !field // note: use of currying paintCell
let f = new Form(Text = "Mines", ClientSize = new Size(w * s, h * s), Visible = true)
f.Paint.Add(fun a -> paintField a.Graphics)
f.MouseDown.Add(fun a ->
let i = a.X / s
let j = a.Y / s
field := if a.Button = MouseButtons.Left then
autoExpose !field [(i, j)]
else
setFlag !field i j
let c = (!field).[i, j]
if fst c && snd c = Expose then // exposed a mine?!
field := Array2.map (fun c -> (fst c, Expose)) !field // expose everything
f.Refresh()
MessageBox.Show("Boooooom!\nTry again.") |> ignore
field := randomField // start over
f.Refresh())
f
Application.Run(form) 9月8日 Syntactic Sugar Is Sweet!
I’m really loving working in F#! It may become my primary language for the next half-decade. Through the early 90s it was C++, the late 90s was spent drinking the Java Kool-Aid®, the 2000s has been C# (with a sad couple-year relapse to C++ in there), now I think I’m ready for a completely different branch of the language tree. ML is a good branch and functional languages are certainly the future - most likely coming to the masses in the form of more popular languages being infused with functional idioms. C# 3.0 is a deliberate step in that direction. Scheme is still my favorite for pure elegance and simple beauty, but for an everyday tool, simplicity of the language itself is not what’s important. I do like the Scheme motto of, “…removing the weaknesses and restrictions that make additional features appear necessary…” and I appreciate that the simplest solution is often also the most general one. Scheme’s exploration of how far this can be taken is amazing. On the other hand, syntactic sugar sure is sweet! F# is wonderfully succinct and powerful! As a professional tool, this is exactly what I’m looking for. At first I felt kind of bad for favoring F# over Scheme; thinking it was my Microsoft influence showing. Microsoft certainly gets it wrong sometimes when it comes to packing too many features into consumer products. However, I think that professional tools, and especially programming languages targeting “high end” users, are very different from casual-use products. The learning curve is steep, but is far outweighed by the all-day-every-day productivity that comes from feature richness. I don’t care if the language is complicated to implement (Don Syme can worry about that). I only care about keeping my solutions written within the language simple. As a professional dev, I want the power to write terse and simple code. Matz (the Ruby guy) said it nicely in Beautiful Code (Chapter 29 – Treating Code As an Essay): “Simplicity is one of the most misunderstood concepts in programming. People who design languages frequently want to keep those languages simple and clean. While the sentiment is noble, doing this can make programs written in that language more complex. … while [Ruby] is far from simple, it supports simple solutions for programming. Because Ruby is not simple, the programs that use it can be.” He also quoted Mike Cowlishaw (the REXX guy): “The general principle is that very few people have to implement interpreters or compilers for a language, whereas millions of people have to use and live with the language. One should therefore optimize for the millions, rather than the few. Compiler writers didn’t love me for that…” 8月12日 So Many Books, So Little Time...
I just finished my second reading of Robert Pickering’s book, Foundations of F#. Why read it twice? Well to be honest, I didn’t understand it the first time More promising is Paul Hudak’s, The Haskell School of Expression which I’m in the middle of at the moment. Not that I really want to learn Haskell (although surprisingly similar to F#). I’m hoping to learn more about the functional way of doing things. It’s a fun book because it teaches enough to get started and immediately jumps into building things in an interesting problem domain (geometry and a graphics library). You begin to feel that each new thing you learn is a new toy to play with; Its fun – as programming should be. Also, as you pick up new tricks, he takes you back to revisit solutions from previous chapters and to refactor out some of the ugliness. You quickly appreciate the value of things that would have otherwise gone unnoticed without having first done it the hard way. This is how teaching a new language should be done. Show a little bit and start using it right away. Then teach a bit more and start using that. Don’t worry too much about presenting each thing exhaustively or in a well-structured order. Also, don’t worry about letting the learner jump in with inadequate knowledge to do things properly. That will only help lead to learning opportunities where you can later add to the toolbox and show how to do it right. |
|
|