| MoMonkey 的个人资料Monkey Coder日志列表网络 | 帮助 |
|
|
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) |
|
|