#+Title: Ruby and F# demo #+Subtitle: September 12th #+Author: Mark Armstrong, PhD Candidate, McMaster University #+Date: Fall, 2019 #+Description: A quick demonstration of the Ruby and F# languages #+Description: for CS-3MI3. #+Property: header-args :tangle yes * What do you need for homework 1? :PROPERTIES: :CUSTOM_ID: What-do-you-need-for-homework-1? :END: To build our number guessing game, you need a few specific capabilities. - Compare numbers. - Branch. - Repeat. - Perform standard input and output. Let's quickly run through the ways to accomplish these in both Ruby and F#. Along the way, we'll try to do some “sight-seeing” in these languages. This means that we'll identify how to perform each task right up front, but then attempt to explore a bit deeper into how the languages are unique. * Tangle headers :noexport: :PROPERTIES: :CUSTOM_ID: Tangle-header :END: #+begin_src ruby =begin This file is simply the “tangled” Ruby code of ruby-f#-demo.org; it is the “raw” source code, provided for your convenience. =end #+end_src #+begin_src fsharp (* This file is simply the “tangled” F# code of ruby-f#-demo.org; it is the “raw” source code, provided for your convenience. *) #+end_src * Ruby :PROPERTIES: :CUSTOM_ID: Ruby :END: ** “About Ruby” :PROPERTIES: :CUSTOM_ID: “About-Ruby” :END: Ruby is an almost purely object-oriented language, heavily inspired by Smalltalk, Lisp and Perl. It's creator, Yukihiro “Matz” Matsumoto, has documented these inspirations in [[http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/179642][a post on ruby-talk]]. #+begin_quote Ruby is a language designed in the following steps: * take a simple lisp language (like one prior to CL). * remove macros, s-expression. * add simple object system (much simpler than CLOS). * add blocks, inspired by higher order functions. * add methods found in Smalltalk. * add functionality found in Perl (in OO way). #+end_quote Ruby was a language born out of Matz's interests and love of certain language features. He's [[https://ruby-doc.org/docs/ruby-doc-bundle/FAQ/FAQ.html][said]] about its creation #+begin_quote Well, Ruby was born on February 24 1993. I was talking with my colleague about the possibility of an object-oriented scripting language. I knew Perl (Perl4, not Perl5), but I didn't like it really, because it had smell of toy language (it still has). The object-oriented scripting language seemed very promising. I knew Python then. But I didn't like it, because I didn't think it was a true object-oriented language ─ OO features appeared to be add-on to the language. As a language manic and OO fan for 15 years, I really wanted a genuine object-oriented, easy-to-use scripting language. I looked for, but couldn't find one. So, I decided to make it. It took several months to make the interpreter run. I put it the features I love to have in my language, such as iterators, exception handling, garbage collection. Then, I reorganized the features of Perl into a class library, and implemented them. I posted Ruby 0.95 to the Japanese domestic newsgroups in Dec. 1995. #+end_quote ** Comparing numbers in Ruby :PROPERTIES: :CUSTOM_ID: Comparing-numbers-in-Ruby :END: #+begin_src ruby :results output x = 10 y = 5 puts x puts y puts x < y puts x > y puts x == y #+end_src #+RESULTS: : 10 : 5 : false : true : false Pretty boring so far. But something that's a bit fascinating about Ruby is that despite this syntax being reminiscent of “procedural” languages such as C, “behind the scenes” (most of) what we are doing is calling methods! Let's make that explicit. #+begin_src ruby :results output x = 10 y = 5 Kernel.puts(x) Kernel.puts(y) Kernel.puts(x.< y) Kernel.puts(x.> y) Kernel.puts(x.== y) #+end_src #+RESULTS: : 10 : 5 : false : true : false Neat! So the “regular” looking syntax is syntactic sugar for this explicit method calling syntax. We can strip away more syntactic sugar; when we call these methods, we are really sending messages. #+begin_src ruby :results output x = 10 y = 5 Kernel.send "puts", x Kernel.send "puts", y Kernel.send "puts", (x.send "<", y) Kernel.send "puts", (x.send ">", y) Kernel.send "puts", (x.send "==", y) #+end_src #+RESULTS: : 10 : 5 : false : true : false That's really cool. Ruby is (almost) purely object-oriented; by that, we mean that (almost) everything is an object, and (almost) everything is message-passing between objects. ** Branching in Ruby :PROPERTIES: :CUSTOM_ID: Branching-in-Ruby :END: Ruby includes the classic branching operator, ~if~. Use the ~end~ keyword to end the body. #+begin_src ruby :results output x = 10000 if x > 1000 then puts "x is impossibly large!" end #+end_src #+RESULTS: : x is impossibly large! To make it a one-liner, use the ~then~ keyword or a ~;~. Or if you don't like that, use braces ~{}~. #+begin_src ruby :results output x = 10000 if x > 1000 then puts "x is impossibly large!" end if x < 100000; puts "But it's not all that impossibly large..." end #+end_src #+RESULTS: : x is impossibly large! : But it's not all that impossibly large... When writing a one-liner, consider using postfix form instead. Note we don't need ~end~ for this form! #+begin_src ruby :results output x = 10000 puts "x is impossibly large!" if x > 1000 puts "But it's not all that impossibly large..." if x < 100000 #+end_src #+RESULTS: : x is impossibly large! : But it's not all that impossibly large... Ruby has both ~else~ and ~elsif~. So no need for the ~else if~ form. #+begin_src ruby :results output has_if = true has_elsif = true has_else = true if false then puts "Whoa, you're in trouble." else if has_elsif then puts "Why didn't you use 'elsif' instead of 'else if' then?" elsif has_if and has_elsif then puts "Ah, I guess you had no choice then." end end #+end_src In addition to ~if~, Ruby includes ~unless~. #+begin_src ruby :results output lacks_unless = false puts "No need for 'if not ...'!" unless lacks_unless #+end_src ** Repeating in Ruby :PROPERTIES: :CUSTOM_ID: Repeating-in-Ruby :END: As you might expect from a language with both ~if~ and ~unless~, Ruby has both ~while~ and ~until~ loops. As with ~then~ for conditionals, the ~do~ is optional unless you want a one-liner. #+begin_src ruby :results output x = 0 while x <= 5 do puts "Counting up with while..." ++ x.to_s x += 1 end until x == 0 do x -= 1 puts "Counting down with until..." ++ x.to_s end #+end_src Ruby also includes a variety of nifty iterating loops. #+begin_src ruby :results output for x in 1..5 do puts "Counting up with for..." ++ x.to_s end 5.times do |x| puts "Repeating a task with times..." ++ x.to_s end [1,2,3,4,5].each do |x| puts "Iterating over a list with each..." ++ x.to_s end #+end_src #+RESULTS: #+begin_example Counting up with for...1 Counting up with for...2 Counting up with for...3 Counting up with for...4 Counting up with for...5 Repeating a task with times...0 Repeating a task with times...1 Repeating a task with times...2 Repeating a task with times...3 Repeating a task with times...4 Iterating over a list with each...1 Iterating over a list with each...2 Iterating over a list with each...3 Iterating over a list with each...4 Iterating over a list with each...5 #+end_example The last example above used a /block/; a unit of code that takes an argument. Also known as an /anonymous function/ or a /lambda/. #+begin_src ruby :results output f = lambda { |x, y| puts "Hey, how'd you call me? I'm not supposed to have a name!" puts "Well, anyway, take your stuff back." puts x puts y } g = f f.("f's","stuff") puts "" g.call "g's", "things" #+end_src ** Performing standard input and output in Ruby :PROPERTIES: :CUSTOM_ID: Performing-standard-input-and-output-in-Ruby :END: We've been using standard output all along here. #+begin_src ruby :results output puts "Hello world" puts "It's nice to meet you" #+end_src #+RESULTS: : Hello world : It's nice to meet you Note that ~puts~ (PUT String) places a newline after the string. ~print~ does not. #+begin_src ruby :results output print "Whatever you do, " print "don't squish these lines together!" #+end_src #+RESULTS: : Whatever you do, don't squish these lines together! # Org users note: # You can evaluate the following code block. # it will seem like gets pulled an input out of thin air. # In fact, it's just a hack; we replace gets with # a random integer using the :var header for the code block. # Unfortunately it will be the same random integer each time we use gets. Standard input is just as easy. #+begin_src ruby :results output :var gets=(random 100) puts "Guess my favourite number." guess = gets puts "How'd you know it was #{guess}?" #+end_src #+RESULTS: : Guess my favourite number. : How'd you know it was 99? That last block showed off the fact that strings delimited by double quotes are formatted strings! Single quote strings are not formatted. #+begin_src ruby :results output will = "will" will_not = "will not" puts "I #{will} be formatted." puts 'I #{will_not} be formatted.' puts "I %s also be formatted. Maybe you like my syntax more." % [will] #+end_src #+RESULTS: : I will be formatted. : I #{will_not} be formatted. : I will also be formatted. Maybe you like my syntax more. ** Where to go in Ruby from here? :PROPERTIES: :CUSTOM_ID: Where-to-go-in-Ruby-from-here? :END: My suggestion is to research *all of these topics* further. Read Musa's cheatsheet. Check out documentation and tutorials. We've barely scratched the surface here, and likely any Ruby expert would say I've missed several important things. For this course, you don't need to become experts, and if there is a idiomatic way to do things in Ruby, then a) it's not as emphasised as it sometimes is for Python, and b) I'm not concerned with you learning it. Instead, focus on - finding ways to do things that are similar to what you do elsewhere, - finding ways to do things that are *completely different* than what you do elsewhere, - and most importantly, having some fun with it. * F# :PROPERTIES: :CUSTOM_ID: F# :END: ** “About F#” :PROPERTIES: :CUSTOM_ID: “About-F#” :END: F# is a member of the ML family of languages, born from a .NET implementation of OCaml . You've likely worked with Haskell, and possibly Elm, both of which inherited several ideas from ML. So, parts of F# will likely be familiar to you, even if you have never seen it before. ** Comparing numbers in F# :PROPERTIES: :CUSTOM_ID: Comparing-numbers-in-F# :END: #+begin_src fsharp :results output let x, y = 5, 10 printfn "%b" (x > y) printfn "%b" (x < y) printfn "%b" (x = y) #+end_src #+RESULTS: : false : true : false : val y : int = 10 : val x : int = 5 You should notice from the application of ~printfn~ that it's curried. #+begin_src fsharp :results output let x, y = 5, 10 let printbool = (printfn "%b") printbool (x < y) printbool (x = y) #+end_src #+RESULTS: : true : false : val y : int = 10 : val x : int = 5 : val printbool : (bool -> unit) Of course ~printfn~ is (what we prefer to call) a procedure, rather than a function, because its primary purpose is the side effect of printing to the console. But does it have a value? We can use ~%O~ (capital letter O) to print any object; let us do so to investigate the value of ~printfn~. #+begin_src fsharp let x = printfn "Hello world" printfn "%O" x #+end_src #+RESULTS: : () ~()~ is an empty tuple. How many different empty tuples can there be? And once you answer that, what's a good name for the type of empty tuples? #+begin_src fsharp printfn "hello" #+end_src #+RESULTS: : () ** Branching in F# :PROPERTIES: :CUSTOM_ID: Branching-in-F# :END: F# has ~if~, ~elif~ and ~else~. Unlike Haskell and Elm, you don't have to provide an ~else~ clause. #+begin_src fsharp :results output if 2 = 5 then printfn "What reality am I in?" if 2 = 7 then printfn "Now I'm even more concerned..." elif 2 = 4 then printfn "This doesn't help either..." else printfn "Okay, maybe everything's okay." #+end_src More interesting than ~if-then-elif-then-else~ is pattern matching. It's a powerful tool when working with algebraic datatypes, which we'll see later on, especially for the assignment. #+begin_src fsharp :results output let r = match 5 with | 0 -> "Factorial is 1." | 1 -> "Factorial is 1." | 2 -> "Factorial is 2." | 3 -> "Factorial is 6." | 4 -> "Factorial is 24." | _ -> "I'm tired, go away." printfn "%s" r #+end_src #+RESULTS: : Factorial is 2. : val r : string = "Factorial is 2." We can attach guards to the cases when pattern matching. #+begin_src fsharp :results output let r = match 5 with | 5 when false -> "It might match, but it can't satisfy this guard!" | 5 when true -> "This guard's easily pleased." | _ -> "Uh... hello?" printfn "%s" r #+end_src #+RESULTS: : This guard's easily pleased. : val r : string = "This guard's easily pleased." Of course pattern matching and guards shine most when defining functions/procedures. #+begin_src fsharp :results output let rec fact n = match n with | 0 -> 1 | n when n > 0 -> n * fact (n - 1) | _ -> printfn "Can't calculate factorial of a negative!" -1 printfn "Factorial 5 is %d" (fact 5) #+end_src #+RESULTS: : Factorial 5 is 1 : val fact : n:int -> int ** Repeating in F# :PROPERTIES: :CUSTOM_ID: Repeating-in-F# :END: We've just defined a recursive function; you know that recursion will allow us to repeat, potentially infinitely. Note the type annotation; we omit them usually, but occasionally the type checker needs our input. #+begin_src fsharp :results none let rec forever (x : int) : int = printfn "All work and no play makes F# a dull language..." forever x #+end_src Then call, for instance, ~forever 10~ to run until you get tired and interrupt it. As far as I can tell, we cannot create such an infinite loop with type ~unit~ (i.e., as a constant, i.e., a non-function). We can cheat a bit an make it a function on ~unit~ instead, (note the type annotation), so then the only possible argument is ~()~ #+begin_src fsharp :results none let rec forever' (_ : unit) : Lazy = printfn "I can do it, I can do it ∞ times!" forever'() #+end_src Then call ~forever'()~ to run until you get tired of it. Given what you know about procedure calls and the memory stack, you might be expecting that this is not truly an infinite loop; eventually, we should run out of room on the memory stack, and the program should crash. But that doesn't happen! The reason is /tail recursion/, which we'll discuss in detail at some point; for now, suffice to say that the F# runtime is smart, and reuses stack space. Now, perhaps you still don't like the idea of infinite recursion. That's okay! F# also includes loops for you. If you want to name the loop, you'd better still mark it ~lazy~. #+begin_src fsharp :results none let forever : Lazy = lazy while true do printfn "Much better..." #+end_src There are also ~for..in~ and ~for..to~ loops you can explore. #+begin_src fsharp :results output for x in [1;2;3] do printfn "Iterating through a list %d" x for x = 1 to 3 do printfn "Counting up %d" x for x = 3 downto 1 do printfn "Counting down %d" x #+end_src #+RESULTS: : Iterating through a list 1 : Iterating through a list 2 : Iterating through a list 3 : Counting up 1 : Counting up 2 : Counting up 3 : Counting down 3 : Counting down 2 : Counting down 1 ** Performing standard input and output in F# :PROPERTIES: :CUSTOM_ID: Performing-standard-input-and-output-in-F# :END: We've been printing formatted strings with ~printfn~. There's also the method ~System.Console.WriteLine()~ for outputting strings (not formatted). # Org readers: I unfortunately don't have a good # hack for faking standard input here. Let me know # if you come up with or find one. Unfortunately, there's no “~writefn~” analagous to ~printfn~. But there is ~System.Console.ReadLine()~. #+begin_src fsharp :results output let value = System.Console.ReadLine() System.Console.WriteLine("Is there an echo in here?"); System.Console.WriteLine(value) #+end_src #+RESULTS: ** Where to go in F# from here? :PROPERTIES: :CUSTOM_ID: Where-to-go-in-F#-from-here? :END: Assuming you are familiar with either Haskell or Elm, explore how F# is similar to and/or differs from them. While they are all within the functional paradigm, F# does not encourage purity in the same way. You may find this makes starting out in the language simpler. Explore concepts you may know from elsewhere in F#, including: - algebraic datatypes, such as trees or lists, - using recursive algorithms instead of iterative algorithms, and - defining higher-order functions; functions which take functions as arguments. If you are not already, *become comfortable with recursion*! F# has loops and mutable state, but some languages we consider later will not! * Leftovers :PROPERTIES: :CUSTOM_ID: Leftovers :END: I wrote this function when starting out with repetition in F#; it doesn't fit the narrative above now, but it can live here as a leftover. #+begin_src fsharp :results none let rec forever : Lazy = let rec forever_helper (so_far : sbyte) : sbyte = match so_far with | 0y -> printfn "Starting out..." forever_helper 1y | n when n % 100y = 0y -> printfn "Took a hundred steps..." forever_helper (n + 1y) | n when n = -1y -> printfn "Sorry, fell asleep and overflowed a bit!" forever_helper (n + 1y) | n -> forever_helper (n + 1y) in lazy forever_helper 0y #+end_src Run it with ~forever.Force()~, if you don't mind looping for a while.