Ruby Lukninger Forklaret.

Ruby lukninger forklaret. foto af Denise Johnson

medmindre du forstod “lukningskonceptet” fra andre sprog, kunne det have været forvirrende for dig oprindeligt i Ruby. “Lukning”, er anakronistisk, undtagen til Ruby (og nogle funktionelle programmeringssprog).

Her er en Merriam-Webster definition af en lukning:

lukning navneord clo·sikker \ ˈklō-zhər \ arkaisk: betyder hoslagt: kabinet

det er Det samme her: Ruby lukning “omslutter en masse kode til at blive henrettet”. Det er et “kabinet”. En lukning er en teknik til implementering af leksikalt scoped navnebinding på et sprog med førsteklasses funktioner.

overvej dette uddrag:

2.times do topic = 'Ruby closures' puts "An adventure with #{topic}."end#=> An adventure with Ruby closures.#=> An adventure with Ruby closures.#=> 2

vi har “lukket” topic og puts mellem do og end. Alt uden for denne region kan ikke have adgang til emne, men puts – metoden kan, fordi den er inde i samme miljø (leksikalt scoped) som den variable tildeling (navnebinding). Det er denne teknik til at kombinere metoder og variabler, vi kalder lukning. Bemærke: { ... } og do ... end er ækvivalente, når de overføres til en metode. Jeg vil bruge dem i flæng.

hvad med dette stykke kode?

lam = lambda do |topic| puts "An adventure with #{topic}."endlam.call('Ruby closures')#=> An adventure with Ruby closures

hvis du ser tæt på, gør det en lignende ting som uddraget før. Hvad med den sidste?

prok = Proc.new do |topic| puts "An adventure with #{topic}."endprok.call('Ruby closures')#=> An adventure with Ruby closures.

disse formularer repræsenterer måder at vedlægge kode i Ruby. Vi har set en blok, lambda og en Proc repræsenteret henholdsvis som: {}, lambda {} og Proc.new {} i de tidligere kodestykker. Disse er lukninger i Ruby. I alle tre har vi sat en lokal variabel topic og sendt den til en lokal metode puts. Du undrer dig måske over, hvor vi indstiller topic variablen i lam og prok. Vi satte det ikke eksplicit som topic = "Ruby closures" som vi gjorde i det allerførste kodestykke. Årsagen er; #call metoden for Proc og lambda gør dette for os. Hvis du ser på dokumentationen for Proc#call, har vi:

opkald (params,…).

en subtil forskel værd at bemærke om Ruby blokke (som lukninger) er, i Ruby, blokke kan ikke repræsenteres som objekter, de bare give en enkel måde at gruppere kode, men du kan ikke binde dem til variabler. I sig selv er de ret ubrugelige. Procs og Lambdas, på den anden side, er, hvad nogle kalder førsteklasses borgere.

blokke.

jeg står til at blive rettet, men som jeg nævnte tidligere, betyder blokke i sig selv ikke noget. De overføres normalt som argumenter til metoder, men de hjælper os også med at gruppere vores kode for at ændre dens adfærd. Her er et eksempel:

(1..10).each { |v| puts v if v.even? }# Output: ## 2# 4# 6# 8# 10

det er nemt at se her, at vi gentager en rækkevidde og udskriver alle lige tal. Nu, hvad hvis vi ønsker at udskrive alle tal, der er lige og prime på samme tid? Her er når blokke viser deres anvendelighed. Vi skal kun ændre koden i blokken for at gøre dette for at få:

require 'prime'(1..20).each { |v| puts v if v.even? && v.prime? }# => 2

vi har lige ændret adfærd vores kode ved at gruppere, hvad der er i vores blok. Vi var nødt til at kræve Prime standardbiblioteket for at gøre brug af prime? – metoden. Vi kunne også have implementeret vores metode til plukning af primtal, hvis vi ville. Vi har set, hvordan vi kan bruge Ruby blocks til at gruppere vores kode til at opregne. Men i dette indlæg taler vi om lukninger. Har du allerede set, hvordan blokken ovenfor fungerer som Lukning ved at tildele hvert nummer i området 1 til 20 til variablen v? Hvis ikke hvad med denne smule kode her:

def shout(word, position) position.times { |x| puts "#{ x }: #{ word }"}endshout("SHOUTING!", 2)# Output: ## 0: SHOUTING!# 1: SHOUTING!

i denne tildeler vi word variablen til strengen SHOUTING!, mens vi tildeler den anden parameter, et heltal, til variablen position og bruger x til at repræsentere hvert tal fra 0 op til position for det antal gange, vi vil have strengen udskrevet. Husk definitionen af lukning. Opfylder den sidste kode det?

nu ved du lidt om blokke, men i de hidtil viste eksempler bruger vi kun Ruby blokke til at indkapsle adfærd og overføre dem til Rubys metoder. Vi kan også skrive vores metoder, der accepterer blokke.

i Ruby er vi velsignet med yield og block_given? metoderne. Disse metoder hjælper os med at skabe vores metoder, der også accepterer blokke. Når som helst du ser yield i Ruby, oversæt det som”Hej, gør hvad den passerede blok siger”. block_given? det er meget intuitivt. Denne metode returnerer simpelthen en boolsk om, hvorvidt der var en blok bestået. Her er et simpelt eksempel for at få konceptet på tværs:

def shout yieldend# Call the #shout method with a block.shout { puts "SHOUTING!" }# => SHOUTING!

prøv metoden ovenfor og ring yield to eller tre gange og se hvad der sker. Giver det nogen mening? Hvis du ikke passerede en blok til denne metode, da du kaldte den, ville du se

ingen blok givet (udbytte) (LocalJumpError)

du kan prøve det. Og det er her block_given? kommer til nytte. Sådan kan du gøre brug af block_given? – metoden:

def shout block_given? ? yield : puts("Sir, you didn't pass any block.")end# Call the #shout method without a block.shout# => Sir, you didn't pass any block.

det er stort set alt, hvad der er til det! Men du undrer dig måske over, hvad den praktiske anvendelse af alt dette er: Pretty Object Initialisation! Sådan her:

module Genus class Species attr_accessor :name, :height, :weight, :ethnicity def initialize yield self if block_given? end endendhuman = Genus::Species.new do |characteristics| characteristics.name = "Emmanuel Hayford" characteristics.height = 188 # cm characteristics.weight = 104 # kg characteristics.ethnicity = "black"endp human.ethnicity# => "black"

vi sætter self (som er et instantieret objekt af Artsklassen) til characteristics variablen.

Procs.

selvom alt i Ruby er et objekt, er konstruktioner som blokke og metoder ikke. Men de kan vi kan gøre dem til objekter med Procs. Så for det meste er proc ‘ er blokke repræsenteret som objekter, som du kan kalde metoder til eller allokere til hukommelsen.

lad os refactor vores allerførste eksempel til at bruge en Proc.

så vi har denne smule kode:

2.times do topic = 'Ruby closures' puts "An adventure with #{topic}."end

ændret til dette:

prok = Proc.new { topic = 'Ruby closures' puts "An adventure with #{topic}."}# `Proc.new` is equivalent to `proc`2.times do prok.callend# => An adventure with Ruby closures.# => An adventure with Ruby closures.

fordi proc nu er et objekt, kan du påkalde call – metoden på den for at returnere blokken.Vi kunne refactor yderligere for at gøre dette mere dynamisk ved at få blokken eller Proc-objektet til at tage et argument:

prok = Proc.new { |topic| puts "An adventure with #{topic}."}2.times do prok.call('Ruby closures')end# => An adventure with Ruby closures.# => An adventure with Ruby closures.

her er noget interessant; for et Proc-objekt, der tager et enkelt argument, kan du for eksempel kalde det på et par forskellige måder. Ruby tillader os at udelade opkaldet til call. Så linje 6 i koden ovenfor svarer til følgende:

prok.('Ruby closures') og prok === 'Ruby closures'.

hvis du imidlertid ønskede at arbejde med flere argumenter, kunne du overføre dine argumenter til Proc-objektet som elementer i et array. Så lad os antage, at dette er vores kode:

prok = Proc.new { |x, y| puts x + y }

alle metoder nedenfor til opkald prok er gyldige:

prok.call prok === prok# => 10# => 10# => 10

alle ovenstående gælder for lambdas.

Lambdas.

Lambdas er Proc-objekter, der respekterer arity og behandler nøgleordet return lidt anderledes.

her er den adfærd, der er værd at bemærke om Procs og Lambdas. Overvej følgende:

cool_lambda = lambda { |first_name, nationality| puts "#{first_name} is #{nationality}."}cool_proc = proc { |first_name, nationality| puts "#{first_name} is #{nationality}."}cool_lambda.call("Matz", "Japanese")cool_proc.call("Aaron", "American")

alt fungerer som forventet. cool_lambdas underskrift accepterer to argumenter, og det er strengt om det, hvis du bestod et mere eller et mindre argument, ville det sprænge med noget som:

forkert antal argumenter (givet 3, forventet 2) (ArgumentError)

cool_proc på den anden side vil bare gøre, hvad blokken angiver og lydløst ignorere ekstra params eller deres fravær.

her er sagen om return nøgleordet med lukninger:

class ClosureExperiment def call_closure(closure) puts "Calling #{ check_closure_type(closure) }." closure.call puts "#{ check_closure_type(closure) } got called!" end def check_closure_type(unknown_closure) unknown_closure.lambda? ? "Lambda" : "Proc" endendclosure = ClosureExperiment.new.call_closure(lambda { return })# => Calling Lambda.# => Lambda got called!closure = ClosureExperiment.new.call_closure(proc { return })# => Calling Proc.

fra ovenstående kan det bemærkes, at mens Procs vil snappe ud straks ved at se return, tøver Lambda, indtil alt i deres sammenhæng udføres.

en yderligere forskel mellem disse to lukninger er, når en Lambda er defineret med et standardargument og bestået et array, returnerer det altid standardargumentet, der er overført til det, mens Proc ignorerer det, medmindre der ikke er nogen ekstra argumenter, så implementeres standardargumentet som i det følgende:

lam = lambda { |a, b = :foo| p a, b }.call()# => , :foo]prok = proc { |a, b = :foo| p a, b }.call()# => prok = proc { |a, b = :foo| p a, b }.call()# => 

det ville være det for, hvordan Procs og Lambdas adskiller sig.

Afslutningsvis vil jeg gerne tænke på Procs som blokke repræsenteret som objekter, mens Lambdas er en variant af Procs, der rejser fejl, når du passerer det forkerte antal argumenter.

Følg mig på kvidre for at udforske Ruby, JavaScript og internet teknologier. Overvej at abonnere på mit nyhedsbrev eller købe mig en kop kaffe, hvis du fandt noget af mit indhold nyttigt.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.