square = ->(x) { x * x } square.(4) # => 16 person = ["Dave",:male] print_person = ->((name,gender)) { puts "#{name} is a #{gender}" } print_person.(person)
insert_person.(name,birthdate,gender) # => id update_person.(new_name,new_birthdate,new_gender,id) delete_person.(id) fetch_person.(id) # => ,
gets
and puts
are built-in functions and work as expected): puts "Name?" name = gets puts "Birthdate?" birthdate = gets puts "Gender?" gender = gets
id
if the validation and insertion was successful, or an error message if something went wrong. Since we have no exceptions, no hash tables (only arrays), we will have to think creatively.nil
) in one of the cells of the array indicates the success or failure of the operation. add_person = ->(name,birthdate,gender) { return [nil,"Name is required"] if String(name) == '' return [nil,"Birthdate is required"] if String(birthdate) == '' return [nil,"Gender is required"] if String(gender) == '' return [nil,"Gender must be 'male' or 'female'"] if gender != 'male' && gender != 'female' id = insert_person.(name,birthdate,gender) [[name,birthdate,gender,id],nil] }
String()
, then this function returns an empty string if the value nil
passed to it. invalid = true while invalid puts "Name?" name = gets puts "Birthdate?" birthdate = gets puts "Gender?" gender = gets result = add_person.(name,birthdate,gender) if result[1] == nil puts "Successfully added person #{result[0][0]}" invalid = false else puts "Problem: #{result[1]}" end end
get_new_person = -> { puts "Name?" name = gets puts "Birthdate?" birthdate = gets puts "Gender?" gender = gets result = add_person.(name,birthdate,gender) if result[1] == nil puts "Successfully added person #{result[0][0]}" result[0] else puts "Problem: #{result[1]}" get_new_person.() end } person = get_new_person.()
if result[1] == nil
, so let's wrap them in a function. The great thing about functions is that they allow you to reuse the structure, not the logic. The structure here is an error check and a call to one of two functions for success or failure. handle_result = ->(result,on_success,on_error) { if result[1] == nil on_success.(result[0]) else on_error.(result[1]) end }
get_new_person
function uses a more abstract way to handle errors: get_new_person = -> { puts "Name?" name = gets.chomp puts "Birthdate?" birthdate = gets.chomp puts "Gender?" gender = gets.chomp result = add_person.(name,birthdate,gender) handle_result.(result, ->((id,name,birthdate,gender)) { puts "Successfully added person #{id}" [id,name,birthdate,gender,id] }, ->(error_message) { puts "Problem: #{error_message}" get_new_person.() } ) } person = get_new_person.()
handle_result
allows handle_result
to explicitly name variables instead of using array indexing. Now we can not only use the friendly name error_message
, but also “split” the array into parts and use it as separate function parameters using the syntax of the form ((id,name,birthdate,gender))
.insert_person
and update_person
: insert_person.(name,birthdate,gender,title) update_person.(name,birthdate,gender,title,id)
add_person
method: add_person = ->(name,birthdate,gender,title) { return [nil,"Name is required"] if String(name) == '' return [nil,"Birthdate is required"] if String(birthdate) == '' return [nil,"Gender is required"] if String(gender) == '' return [nil,"Gender must be 'male' or 'female'"] if gender != 'male' && gender != 'female' id = insert_person.(name,birthdate,gender,title) [[name,birthdate,gender,title,id],nil] }
get_new_person
. Khm: get_new_person = -> { puts "Name?" name = gets.chomp puts "Birthdate?" birthdate = gets.chomp puts "Gender?" gender = gets.chomp puts "Title?" title = gets.chomp result = add_person.(name,birthdate,gender,title) handle_result.(result, ->((name,birthdate,gender,title,id)) { puts "Successfully added person #{id}" [id,name,birthdate,gender,title,id] }, ->(error_message) { puts "Problem: #{error_message}" get_new_person.() } ) }
get_new_person
does not have to worry about specific fields at all. The function should simply read them and then transfer them to add_person
. Let's see how we can fix this if we move the code into several new functions: read_person_from_user = -> { puts "Name?" name = gets.chomp puts "Birthdate?" birthdate = gets.chomp puts "Gender?" gender = gets.chomp puts "Title?" title = gets.chomp [name,birthdate,gender,title] } person_id = ->(*_,id) { id } get_new_person = -> { handle_result.(add_person.(*read_person_from_user.()) ->(person) { puts "Successfully added person #{person_id.(person)}" person }, ->(error_message) { puts "Problem: #{error_message}" get_new_person.() } ) }
read_person_from_user
and person_id
. Now we don’t need to change get_new_person
if we want to add more fields to the record.*
in the code, here is a brief explanation: *
allows you to give an array for the list of arguments and vice versa. In person_id
we use the parameter list *_, id
, which tells Ruby to put all the arguments, except the last, into the _
array (we are not interested in this array, therefore this name), and put the last one into the id
variable. This only works in Ruby 1.9; in 1.8 only with the last argument you can use the syntax *
. Then when we call add_person
, we use *
with the result read_person_from_user
. Since read_person_from_user
returns an array, we want to use this array as a list of arguments, since add_person
accepts explicit arguments. That is what it does *
. Fine!read_person_from_user
and person_id
still quite strongly interconnected. They both know how we store data. Moreover, if we added new features for processing data from our database, we would have to use functions that also know about the internal structure of the array.Hash
by this point, but we cannot use them. Can we make a real data structure with only functions available? It turns out yes, we can, if we create a function that considers its first argument as an attribute of the data structure: new_person = ->(name,birthdate,gender,title,id=nil) { return ->(attribute) { return id if attribute == :id return name if attribute == :name return birthdate if attribute == :birthdate return gender if attribute == :gender return title if attribute == :title nil } } dave = new_person.("Dave","06-01-1974","male","Baron") puts dave.(:name) # => "Dave" puts dave.(:gender) # => "male"
new_person
acts as a constructor, but instead of returning an object (which we don’t have), it returns a function that, when invoked, can return us the values ​​of various attributes of a particular entry. class Person attr_reader :id, :name, :birthdate, :gender, :title def initialize(name,birthdate,gender,title,id=nil) @id = id @name = name @birthdate = birthdate @gender = gender @title = title end end dave = Person.new("Dave","06-01-1974","male","Baron") puts dave.name puts dave.gender
class
meannew
with the class name calls the initialize
method@
before the variable name makes it a private class instance variableattr_reader
read_person_from_user = -> { puts "Name?" name = gets.chomp puts "Birthdate?" birthdate = gets.chomp puts "Gender?" gender = gets.chomp puts "Title?" title = gets.chomp new_person.(name,birthdate,gender,title) } add_person = ->(person) { return [nil,"Name is required"] if String(person.(:name)) == '' return [nil,"Birthdate is required"] if String(person.(:birthdate)) == '' return [nil,"Gender is required"] if String(person.(:gender)) == '' return [nil,"Gender must be 'male' or 'female'"] if person.(:gender) != 'male' && person.(:gender) != 'female' id = insert_person.(person.(:name),person.(:birthdate),person.(:gender),person.(:title)) [new_person.(person.(:name),person.(:birthdate),person.(:gender),person.(:title),id),nil] } get_new_person = -> { handle_result.(add_person.(read_person_from_user.()), ->(person) { puts "Successfully added person #{person.(:id)}" person }, ->(error_message) { puts "Problem: #{error_message}" get_new_person.() } ) }
add_person
now looks less beautiful because of the syntax for obtaining an attribute, but now we can easily add new fields, preserving the structure of the program. new_person = ->(name,birthdate,gender,title,id) { return ->(attribute) { return id if attribute == :id return name if attribute == :name return birthdate if attribute == :birthdate return gender if attribute == :gender return title if attribute == :title if attribute == :salutation if String(title) == '' return name else return title + " " + name end end nil } }
new_person = ->(name,birthdate,gender,title,id) { return ->(attribute) { return id if attribute == :id return name if attribute == :name return birthdate if attribute == :birthdate return gender if attribute == :gender return title if attribute == :title if attribute == :salutation if String(title) == '' return name else return title + " " + name end elsif attribute == :update update_person.(name,birthdate,gender,title,id) elsif attribute == :destroy delete_person.(id) end nil } } some_person.(:update) some_person.(:destroy)
new_employee = ->(name,birthdate,gender,title,employee_id_number,id) { person = new_person.(name,birthdate,gender,title,id) return ->(attribute) { return employee_id_number if attribute == :employee_id_number return person.(attribute) } }
add_person
function add_person
. She calls insert_person
to put a record in the database and gets the ID back. Then we need to create a completely new record in order to just set the ID. In a classic OOP, we would simply write person.id = id
. new_person = ->(name,birthdate,gender,title,id=nil) { return ->(attribute,*args) { return id if attribute == :id return name if attribute == :name return birthdate if attribute == :birthdate return gender if attribute == :gender return title if attribute == :title if attribute == :salutation if String(title) == '' return name else return title + " " + name end end if attribute == :with_id # <=== return new_person.(name,birthdate,gender,title,args[0]) end nil } }
add_person
even simpler: add_person = ->(person) { return [nil,"Name is required"] if String(person.(:name)) == '' return [nil,"Birthdate is required"] if String(person.(:birthdate)) == '' return [nil,"Gender is required"] if String(person.(:gender)) == '' return [nil,"Gender must be 'male' or 'female'"] if person.(:gender) != 'male' && person.(:gender) != 'female' id = insert_person.(person.(:name),person.(:birthdate),person.(:gender),person.(:title)) [new_person.(:with_id,id),nil] # <==== }
person.id = id
, but it looks decent enough to be readable. The code from it became only better. first = ->((f,*rest)) { f } # or should I name this car? :) rest = ->((f,*rest)) { rest }
empty_map = [] add = ->(map,key,value) { [key,value,map] } get = ->(map,key) { return nil if map == nil return map[1] if map[0] == key return get.(map[2],key) }
map = add.(empty_map,:foo,:bar) map = add.(map,:baz,:quux) get.(map,:foo) # => :bar get.(map,:baz) # => :quux get.(map,:blah) # => nil
people = add.(empty_map ,:insert ,insert_person) people = add.(people ,:update ,update_person) people = add.(people ,:delete ,delete_person) people = add.(people ,:fetch ,fetch_person) people = add.(people ,:new ,new_person) add_person = ->(person) { return [nil,"Name is required"] if String(person.(:name)) == '' return [nil,"Birthdate is required"] if String(person.(:birthdate)) == '' return [nil,"Gender is required"] if String(person.(:gender)) == '' return [nil,"Gender must be 'male' or 'female'"] if person.(:gender) != 'male' && person.(:gender) != 'female' id = get(people,:insert).(person.(:name), person.(:birthdate), person.(:gender), person.(:title)) [get(people,:new).(:with_id,id),nil] }
new_person
course, we could replace the implementation of new_person
map, but it’s more convenient to have an explicit list of attributes that we support, so leave new_person
as is.include
is a great Ruby feature that allows modules to be inserted into the current scope so as not to use explicit namespace resolution. Can we do it here? Close: include_namespace = ->(namespace,code) { code.(->(key) { get(namespace,key) }) } add_person = ->(person) { return [nil,"Name is required"] if String(person.(:name)) == '' return [nil,"Birthdate is required"] if String(person.(:birthdate)) == '' return [nil,"Gender is required"] if String(person.(:gender)) == '' return [nil,"Gender must be 'male' or 'female'"] if person.(:gender) != 'male' && person.(:gender) != 'female' include_namespace(people, ->(_) { id = _(:insert).(person.(:name), person.(:birthdate), person.(:gender), person.(:title)) [_(:new).(:with_id,id),nil] } }
include
only to print less, when we can achieve the same behavior simply by using functions.Source: https://habr.com/ru/post/148076/
All Articles