Skip to content
Menu
CDhistory
CDhistory

Bindingen en Lexical Scope in Ruby

Posted on november 10, 2021 by admin

Gelukkig nieuwjaar, en welkom terug bij Ruby Magic! In deze winteraflevering duiken we in bindingen en scopes. Dus trek je ski’s aan en volg ons tot diep in het bos.

De vorige keer hebben we gekeken naar closures in Ruby door blokken, procs en lambdas te vergelijken. Afgezien van de verschillen tussen de drie typen, raakten we aan wat een closure definieert.

Een closure is een eersteklas functie met een omgeving. De omgeving is een mapping naar de variabelen die bestonden toen de closure werd gemaakt. De closure behoudt zijn toegang tot deze variabelen, zelfs als ze in een ander bereik zijn gedefinieerd.

We hebben Ruby’s equivalent van eersteklas functies verkend, maar we hebben handig omgevingen overgeslagen. In deze aflevering zullen we kijken hoe die omgeving werkt voor closures, klassen en klasse-instanties door te onderzoeken hoe Ruby lexical scope afhandelt via zijn bindingen.

  • Lexical Scope
  • Inherited Scopes and Proc Bindings
  • Bindingen
  • Een voorbeeld uit het echte leven
  • Zijn we je kwijtgeraakt in het bos?

Lexical Scope

In programmeren verwijst scope naar de bindingen die beschikbaar zijn op een specifiek deel van de code. Een binding, of naam binding, bindt een naam aan een geheugenverwijzing, zoals de naam van een variabele aan zijn waarde. De scope definieert wat self betekent, de methoden die kunnen worden aangeroepen, en de variabelen die beschikbaar zijn.

Ruby, net als de meeste moderne programmeertalen, maakt gebruik van een statische scope, vaak lexical scope genoemd (in tegenstelling tot dynamische scope). De huidige scope is gebaseerd op de structuur van de code en bepaalt de variabelen die beschikbaar zijn op specifieke delen van de code. Dit betekent dat de scope verandert wanneer code tussen methodes, blokken en klassen springt-omdat ze allemaal verschillende lokale variabelen kunnen hebben, bijvoorbeeld.

1 2 3 4 5 6
def bar foo = 1 foo end bar # => 1 

In deze methode maken we een lokale variabele binnen een methode en drukken deze af naar de console. De variabele is in scope binnen de methode, aangezien deze daar is aangemaakt.

1 2 3 4 5 6 7
foo = 1 def bar foo end bar # => NameError (undefined local variable or method `foo' for main:Object) 

In dit voorbeeld maken we de variabele buiten de methode aan. Wanneer we de variabele binnen een methode oproepen, krijgen we een foutmelding, omdat de variabele buiten scope is. Lokale variabelen hebben een beperkte reikwijdte, wat betekent dat een methode geen toegang heeft tot een variabele buiten zichzelf, tenzij deze als argument wordt doorgegeven.

1 2 3 4 5 6 7
@foo = 1 def bar @foo end bar # => 1 

Lokale variabelen zijn lokaal beschikbaar, maar instance-variabelen zijn beschikbaar voor alle methoden van een klasse-instantie.

Inherited Scopes and Proc Bindings

Zoals we in de vorige voorbeelden hebben gezien, is de scope gebaseerd op de plaats in de code. Een lokale variabele gedefinieerd buiten een methode is niet in scope binnen de methode, maar kan beschikbaar worden gemaakt door er een instantie variabele van te maken. Methoden hebben geen toegang tot lokale variabelen die buiten hen zijn gedefinieerd, omdat methoden hun eigen scope hebben, met hun eigen bindings.

Procs (inclusief blokken en lambda’s, bij uitbreiding) zijn anders. Telkens wanneer een proc wordt geïnstantieerd, wordt een binding gemaakt die verwijzingen erft naar de lokale variabelen in de context waarin het blok is gemaakt.

1 2
foo = 1 Proc.new { foo }.call # => 1 

In dit voorbeeld stellen we een variabele met de naam foo in op 1. Intern maakt het Proc-object dat op de tweede regel is gemaakt een nieuwe binding. Wanneer we de proc aanroepen, kunnen we om de waarde van de variabele vragen.

Omdat de binding wordt aangemaakt wanneer de proc wordt geïnitialiseerd, kunnen we de proc niet aanmaken voordat we de variabele hebben gedefinieerd, zelfs niet als het blok pas wordt aangeroepen nadat de variabele is gedefinieerd.

1 2 3
proc = Proc.new { foo } foo = 1 proc.call # => NameError (undefined local variable or method `foo' for main:Object) 

Het aanroepen van de proc zal een NameError opleveren omdat de variabele niet is gedefinieerd in de bindingen van de proc. Variabelen die in een proc worden aangeroepen moeten dus worden gedefinieerd voordat de proc wordt aangemaakt of als argument wordt doorgegeven.

1 2 3 4
foo = 1 proc = Proc.new { foo } foo = 2 proc.call # => 2 

We kunnen de variabele echter wijzigen nadat deze in de hoofdcontext is gedefinieerd, aangezien de binding van de proc een verwijzing naar de variabele bevat in plaats van deze te kopiëren.

1 2 3
foo = 1 Proc.new { foo = 2 }.call foo #=> 2 

In dit voorbeeld kunnen we zien dat de variabele foo naar hetzelfde object wijst als het zich in de proc bevindt of erbuiten. We kunnen het binnen de proc bijwerken om de variabele erbuiten ook te laten bijwerken.

Bindingen

Om de huidige scope bij te houden, gebruikt Ruby bindingen, die de uitvoeringscontext inkapselen op elke positie in de code. De binding methode retourneert een Binding object dat de bindingen op de huidige positie beschrijft.

1 2
foo = 1 binding.local_variables # => 

Het binding object heeft een methode genaamd #local_variables die de namen retourneert van alle lokale variabelen die beschikbaar zijn in het huidige bereik.

1 2
foo = 1 binding.eval("foo") # => 1 

Code kan worden geëvalueerd op de binding met behulp van de methode #eval. Het bovenstaande voorbeeld is niet erg nuttig, aangezien het simpelweg oproepen van foo hetzelfde resultaat zou opleveren. Maar omdat een binding een object is dat kan worden doorgegeven, kan het voor interessantere dingen worden gebruikt. Laten we eens kijken naar een voorbeeld.

Een voorbeeld uit het echte leven

Nu we geleerd hebben over bindingen in de veiligheid van onze garage, willen we ze graag meenemen naar de piste en wat spelen in de sneeuw. Afgezien van Ruby’s interne gebruik van bindingen door de taal heen, zijn er enkele situaties waarin bindende objecten expliciet worden gebruikt. Een goed voorbeeld is ERB-Ruby’s templating systeem.

1 2 3 4 5 6 7 8 9 10
require 'erb' x = 1 def y 2 end template = ERB.new("x is <%= x %>, y() returns <%= y %>, self is `<%= self %>`") template.result(binding) # => "x is 1, y() returns 2, self is `main`" 

In dit voorbeeld maken we een variabele aan met de naam x, een methode met de naam y, en een ERB template dat naar beide verwijst. Vervolgens geven we de huidige binding door aan ERB#result, die de ERB tags in het template evalueert en een string teruggeeft met de ingevulde variabelen.

Onder de motorkap gebruikt ERB Binding#eval om de inhoud van elke ERB tag te evalueren in het bereik van de doorgegeven binding. Een vereenvoudigde implementatie die werkt voor bovenstaand voorbeeld zou er als volgt uit kunnen zien:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
class DiyErb def initialize(template) @template = template end def result(binding) @template.gsub(/<%=(.+?)%>/) do binding.eval() end end end x = 1 def y 2 end template = DiyErb.new("x is <%= x %>, y() returns <%= y %>, self is `<%= self %>`") template.result(binding) # => "x is 1, y() returns 2, self is `main`" 

De DiyErb klasse neemt bij initialisatie een template string aan. De methode #result vindt alle ERB-tags en vervangt deze door het resultaat van de evaluatie van de inhoud. Om dat te doen, roept hij Binding#eval aan op de doorgegeven binding, met de inhoud van de ERB tags.

Door de huidige binding door te geven bij het aanroepen van de #result methode, kunnen de eval aanroepen toegang krijgen tot de variabelen die buiten de methode zijn gedefinieerd, en zelfs buiten de klasse, zonder dat ze expliciet hoeven te worden doorgegeven.

Zijn we je kwijtgeraakt in het bos?

We hopen dat je genoten hebt van onze skitocht in het bos. We zijn dieper ingegaan op scopes en sluitingen, nadat we ze hadden verdoezeld. We hopen dat we u niet kwijt zijn geraakt in het bos. Laat het ons weten als je meer wilt leren over bindingen, of een ander Ruby-onderwerp hebt waar je in wilt duiken.

Bedankt voor het volgen van ons en schop alsjeblieft de sneeuw van je bindingen voordat je ze achterlaat voor de volgende ontwikkelaar. Als je deze magische uitstapjes leuk vindt, wil je je misschien abonneren op Ruby Magic om een e-mail te ontvangen wanneer we ongeveer een keer per maand een nieuw artikel publiceren.

Geef een antwoord Antwoord annuleren

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *

Recente berichten

  • Acela is terug: NYC of Boston voor $99
  • OMIM Entry – # 608363 – CHROMOSOME 22q11.2 DUPLICATION SYNDROME
  • Kate Albrecht’s Parents – Learn More About Her Father Chris Albrecht And Mother Annie Albrecht
  • Temple Fork Outfitters
  • Burr (roman)

Archieven

  • februari 2022
  • januari 2022
  • december 2021
  • november 2021
  • oktober 2021
  • september 2021
  • augustus 2021
  • juli 2021
  • juni 2021
  • mei 2021
  • april 2021
  • DeutschDeutsch
  • NederlandsNederlands
  • SvenskaSvenska
  • DanskDansk
  • EspañolEspañol
  • FrançaisFrançais
  • PortuguêsPortuguês
  • ItalianoItaliano
  • RomânăRomână
  • PolskiPolski
  • ČeštinaČeština
  • MagyarMagyar
  • SuomiSuomi
  • 日本語日本語
©2022 CDhistory | Powered by WordPress & Superb Themes