Zoeken…


Opmerkingen

In VFP zijn operators gegroepeerd in die:

  • Numerieke operatoren
  • Logische operatoren
  • Karakteroperators
  • Datum- en tijdoperators
  • Relationele operators

Er zijn ook operatoren, geïmplementeerd als functies (zoals bitgewijze bewerkingen, objectvergelijking ...).

We zullen elk voorbeeld bekijken.

Numerieke operatoren

Numerieke operatoren zijn het gemakkelijkst en bijna hetzelfde als in andere talen.

  • +, -, * en /. Optellen, aftrekken, vermenigvuldigen en delen (in VFP is er geen geheel getal delen, u kunt een resultaat omzetten in geheel getal met de functies INT (), CEILING () en FLOOR ()).
  • % Modulus-operator.
  • ^ en **. Macht van operator (s). Ze doen allebei hetzelfde.
  • (). Operators groeperen.
  • Operators hebben voorrang. De volgorde is:
   ( )
   ^ (or **) 
   / and *
   - and +
    ? 10 / 5 + 2 && Outputs 4
    ? 2 + 10 / 5 && Outputs 4 as well. Division has precedence.
     
    * Both multiplication and division have same precedence
    * They would be interpreted from left to right.
    ? 4 * 5 / 2 + 5 && Outputs 15
    * Use parentheses whenever you are in doubt or want to be explicit
    ? ( (4 * 5) / 2 ) + 5 && Outputs 15. Explicit grouping of operations

    ? 4 * 5^2 && ^ has precedence, this is same as 4 * (5^2) = 100.
    ? (4 + 5)^2 && Using parentheses we say add 5 to 4 (9) and then square = 81. 

Logische operatoren

Logische operatoren in VFP, in hun volgorde van prioriteit zijn:

operator Beschrijving
() Haakjes, groepsexpressies
NIET, ! Logisch de uitdrukking ontkennen. NIET of! heeft geen verschil.
EN Logisch EN de uitdrukkingen
OF Logisch OF de uitdrukkingen
<>,! =, # Controleer op ongelijkheid. Dus hetzelfde als logische exclusieve OF - XOR

Historisch gezien worden NIET, EN, OF geschreven als .NOT., .AND., .OR. Je kunt ze nog steeds gebruiken als je wilt, maar AND, OR, NOT is eenvoudiger en schoner.

Voor vals en waar moet je gebruik maken van F. en .T. letterlijke respectievelijk. Je kunt er niet voor kiezen om F en T te gebruiken.

* Some logical variables
local llOld, llEmail  && any variable declaration implicitly initializes the variable as .F. - false
? m.llOld, m.llEmail && Prints .F. .F.

llOld   = .T.
llEmail = .F.

if ( m.llOld AND m.llEmail )
   ? 'Old AND should be emailed to'
endif
if ( m.llOld OR m.llEmail )
   ? 'Old OR should be emailed to'
endif
if ( m.llOld AND !m.llEmail ) && Same as (m.llOld AND NOT m.llEmail)
   ? 'Old BUT should NOT be emailed to'
endif

* Above code outputs
Old OR should be emailed to
Old BUT should NOT be emailed to

In VFP worden logische uitdrukkingen op een kortere manier geëvalueerd. Dat wil zeggen, als het eerste deel van de controle aan het hele resultaat voldoet, wordt de rest van de uitdrukking niet eens geïnterpreteerd. Een voorbeeld volgt:

? 1 = '2' && An obvious error. It would complain operator/operand type mismatch.

* However we could use such an expression in an if and get no error
* because it is not interpreted at all 
* (VFP is dynamic and there is no compile time check)

local llProcess
llProcess = .T.

if (m.llProcess OR (1='2'))
   ? 'Should do processing'
endif

* Would output

Should do processing

* without any error because m.llProcess true means
* the whole expression would be true, thus the expression after OR 
* is not interpreted at all.

Een valkuil die nieuwkomers opvalt, is dat je soms meerdere controles nodig hebt, bijvoorbeeld in een SQL-query, die zijn gekoppeld aan operatoren AND, OR. Als het er veel zijn, zou je het feit kunnen negeren dat operators een prioriteit hebben (in volgorde (), NIET, EN, OF) en denken dat de interpretatie van links naar rechts in een keten zou worden gedaan. Overweeg een voorbeeld:

select * from myTable where !isCustomer AND debit > 5000 OR discount > 5

wat is de bedoeling van deze vraag? Als we het expliciet maken met behulp van haakjes groeperen, staat er:

((NOT isCustomer) AND debit > 5000) OR discount > 5

vereenvoudigd lijkt het op "firstExpression" OF (korting> 5). Wat de bedoeling ook was, hierdoor zou OF het selecteren:

alle rijen met (korting> 5) - en ook rijen met een klant met meer dan 5000 debitering.

Waarschijnlijk was de bedoeling "geef mij die waar het GEEN klant is EN (debet is meer dan 5000 OF korting is meer dan 5)". Het zou vanaf het begin duidelijk zijn als we haakjes gebruiken:

select * from myTable where !isCustomer AND (debit > 5000 OR discount > 5)

U mag haakjes gebruiken voor de initiële NOT-operator, maar het is niet de moeite waard om deze te gebruiken als de operand een enkele uitdrukking is, leesbaar genoeg met zijn prioriteit -! IsCustomer wordt duidelijk gelezen als (NOT isCustomer).

Karakteroperatoren

Er zijn slechts 4 tekens in volgorde van prioriteit:

operator Beschrijving
() Haakjes voor groeperen. Opmerking: VFP-documentatie, die ik heb, mist deze. Zonder dit is - operator bijna altijd nutteloos.
+ Hiermee worden tekenreeksen naast elkaar geplaatst (samengevoegd).
- Hiermee worden tekenreeksen samengevoegd door de volgspaties van de linker reeks naar het einde van de rechter reeks te verplaatsen .
$ Controleert of de eerste string in de tweede staat.

+ is de eenvoudigste en wordt ook gebruikt om tekenreeksen in vele andere talen samen te voegen.

local firstName, lastName
firstName = "John"
lastName  = "Smith"

? m.firstName + " " + m.lastName

Uitgangen: John Smith

- is een beetje lastig en niet algemeen bekend. Het neemt volgspaties van de linker string, voegt die spaties toe aan de string aan de rechterkant. Stel dat u een tabel hebt met voor- en achternaam en dat elk 20 tekens heeft. We willen de voor- en achternaam samenvoegen om een volledige naam te maken en we willen ook dat de resulterende grootte wordt vastgesteld (in dit geval 20 + 20 + 1 spatie = 41). Laten we ervoor zorgen dat u ook een middelste naamkolom hebt en we willen dat de volledige naam eruit ziet als "achternaam, voornaam middle-naam_______". Het is het gemakkelijkst om dit te doen met - operator, maar je moet rekening houden met de truc om haakjes hier te gebruiken voor groepering, zodat we precies krijgen wat we willen:

* Create a cursor for our sample and fill in a few names
Create Cursor Names (firstName c(20), midName c(20), lastName c(20))

Insert Into Names (firstName, midName, lastName) Values ('Cetin','', 'Basoz')
Insert Into Names (firstName, midName, lastName) Values ('John', 'M', 'Smith')
Insert Into Names (firstName, midName, lastName) Values ('John', 'F', 'Kennedy')
Insert Into Names (firstName, midName, lastName) Values ('Tom', '', 'Hanks')

* Select with tricky - operator
Select *, ;
    lastName - (', '+firstName-(' '+midName)) As FullName ;
    from Names ;
    INTO Cursor crsNames ;
    nofilter

Browse

En de uitgang is als volgt:

Voornaam midName achternaam voor-en achternaam
Cetin Basoz Basoz, Cetin
John M smid Smith, John M
John F Kennedy Kennedy, John F
Tom Hanks Hanks, Tom

In de kolom volledige naam worden alle volgspaties mooi tot het einde geduwd. Als u aanvinkt dat de kolom structuur volledige naam 63 tekens breed is (3 * 20 + 3 tekens die we hebben toegevoegd).

Let op het belang van het groeperen van haakjes (probeer haakjes te verwijderen of op een andere manier te rangschikken).

Hoewel - de operator in dergelijke gevallen verleidelijk kan zijn om te gebruiken, is er een andere kant van de medaille. Deze operator is VFP-specifiek en dus is de SQL niet draagbaar. U kunt in plaats daarvan hetzelfde resultaat bereiken met deze ANSI-compatibele SQL:

Select *, ;
    CAST(RTRIM(lastName) +', '+ RTRIM(firstName) +' '+ midName as char(63)) As FullName ;
    from Names ;
    INTO Cursor crsNames ;
    nofilter

Laatste operator is $. Het controleert eenvoudig of de linker string deel uitmaakt van de rechter string.

local upcased, digits, hexDigits
upcased = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
digits  = '0123456789'
hexDigits  = m.digits + 'ABCDEF'

? 'A' $ m.upcased && .T.
? 'a' $ m.upcased && .F.
? '1' $ m.digits && .T.
? 'F' $ m.digits && .F.
? 'F' $ m.hexDigits && .T.

Belangrijk: in VFP zijn tekenreeksen altijd hoofdlettergevoelig, hoewel u uw code in elk geval kunt schrijven (bovenste, onderste of gemengde). Bijvoorbeeld: "Smith" en "smith" zijn twee verschillende waarden. Of in uw tabel als er een landenkolom is, zou u 'USA' niet vinden als u ernaar zoekt met 'usa'. Hetzelfde geldt voor $ operator, "GE" $ "Duitsland" is onwaar.

Persoonlijke opmerking: hoewel je misschien $ leuk vindt om zijn eenvoud en je het vaak gebruikt in de broncodes van Microsoft, is IMHO van weinig waarde. Als ik denk aan vele vele duizenden regels die ik in mijn carrier heb geschreven, denk ik dat ik er maar weinig voor zou komen in mijn eigen code. Bijna altijd is er een beter alternatief (vooral wanneer de linker operand geen enkel karakter is en / of hoofdlettergevoeligheid belangrijk is).

Datum- en tijdoperators

Er zijn in principe twee operatoren voor datum, datetime-waarden. + en - zijn overbelast (waarschijnlijk een C-term) om datum / datetime wiskunde te doen:

operator Beschrijving
+ Voegt dagen (datum) of seconden (datetime) toe aan een datum / datetime-waarde.
- Hiermee wordt het verschil tussen twee datum / datetime-waarden opgehaald. Trekt dagen (datum) of seconden (datetime) af van datetime-waarden.

+ is de gemakkelijkste. Het heeft twee operanden, de ene is een datum- of datetime-waarde en de andere is een numeriek (hoewel u een willekeurige numeriek kunt gebruiken, is het een geheel getal voor alle praktische doeleinden).

Wanneer een van de operanden een datum is, wordt de numerieke operand als "dag" beschouwd:

? Date() + 10 && Get the date 10 days later
* VFP is leap year aware when doing date math
? Date(2016, 2, 28) + 1 && Add 1 day to Feb 28, 2016. Returns Feb 29, 2016.
? Date(2017, 2, 28) + 1 && Add 1 day to Feb 28, 2017. Returns Mar 1, 2017.

Wanneer een van de operanden een datetime is, wordt de numerieke operand als "tweede" beschouwd:

Er zijn 24 * 60 * 60 = 86400 seconden per dag

? Datetime() + 86400 && Add 1 day to current datetime. 

Voeg 4 uur en 15 minuten toe aan 1 januari 2016 14:20 uur

? Datetime(2016, 1, 1, 14, 20, 0) + (4 * 3600 + 15 * 60) 

Uitgangen Vrijdag 1 januari 2016, 18:35:00 uur.

Met een eenvoudige afdruk via? Is wat u op uw scherm ziet afhankelijk van uw datuminstellingen. Als u bijvoorbeeld niets hebt gewijzigd, zijn uw datuminstellingen Amerikaans (MDY), 12-uursnotatie (AM / PM) en wordt de eeuw alleen weergegeven met de laatste 2 cijfers.

Er is een speciaal symbool ^ voor datum en datetijden waardoor een string 'strikt' moet worden geïnterpreteerd als jjjj / MM / dd [HH: mm: ss | hh: mm: ss tt] -formaat. Daarom kan ^ ook als datum / datum / tijd-operator worden beschouwd. Houd er bijvoorbeeld rekening mee dat sommige gegevens afkomstig zijn van een bron in een indeling zoals 201610082230 (jjjjMMddHHmm). Om die waarde als geldige Datetime te krijgen:

Local cSample, tSample
cSample = '201610082230'
tSample = Ctot(Transform(m.cSample, '@R ^9999/99/99 99:99'))
? Transform(m.tSample, '@YL')

Uitgangen (afhankelijk van de lange datuminstelling van uw systeem):

Zaterdag 8 oktober 2016, 22.30 uur

- wordt gebruikt voor aftrekken. De operanden zijn beide datum / datetime-waarden OF één is een datum / datetime en de andere is een numeriek.

Laten we beginnen met de eenvoudigere datum / datetime en numerieke operanden (zoals met + operator):

Wanneer een van de operanden een datum is, wordt de numerieke operand als "dag" beschouwd:

? Date() - 10 && What was the date 10 days ago?
? Date(2016, 3, 1) - 1 && Returns Feb 29, 2016.
? Date(2017, 3, 1) - 1 && Returns Feb 28, 2017.

Wanneer een van de operanden een datetime is, wordt de numerieke operand als "tweede" beschouwd:

? Datetime() - 86400 && Go back exactly one day

Krijg 1 uur en 30 minuten geleden van "nu":

? Datetime() - (1 * 3600 + 30 * 60) 

Tweede vorm is om het verschil te krijgen tussen twee datum / datetime-waarden. Operanden zijn zowel datum als datum / tijd, u kunt datum en datum / tijd niet tegelijkertijd gebruiken (typ conversie indien nodig, VFP doet dat niet voor u). Regels zijn als in + en -, operanden zijn datum, het verschil is in dagen , operanden zijn datetime en het verschil is in seconden .

Hoeveel dagen tot oudejaarsavond (voor jaar 2016)?

? Date(2016, 12, 31) - Date()

Hoeveel seconden resterend tot middernacht?

? Dtot(Date()+1) - Datetime()

In het laatste voorbeeld hebben we een Date / Datetime-functie, DTOT - DateToTime gebruikt om de middernachtwaarde van morgen te krijgen. Er zijn veel handige datum / datetime-functies beschikbaar, we hebben ze allemaal overgeslagen omdat ze technisch gezien niet als operators worden beschouwd (hoewel ze op datum / datetijden werken :) Hetzelfde geldt ook voor andere operators.

De datum / datum / tijd-aftrekking is ondertekend . Dat is als u de kleinere datum / datetime als de eerste operand gebruikt, dan zou het resultaat negatief zijn. U kunt de functie abs () gebruiken als u een positief resultaat moet krijgen, ongeacht de volgorde van datum / datum.

Relationele operatoren

Van alle operatoren zijn relationele operatoren de meest complexe, daarom hebben we ze tot het einde verlaten.

Relationele operatoren worden ook wel vergelijkingsoperatoren genoemd, ze worden gebruikt om dingen te vergelijken.

Vergelijkingsresultaat is boolean false of true.

Interessant is echter dat als je het in VFP aanvinkt, je alleen een korte lijstbewerkingen en nog een paar regels ziet alsof het allemaal om die operatoren gaat.

Nou, de complexiteit komt van het feit dat ze op elk type werken, of het nu een cijfer, datum, datetime, logisch of een string is, en zelfs op objecten. Bovendien kan het gedrag er ongemakkelijk uitzien, je krijgt niet wat je verwacht tenzij je weet welke effecten de resultaten hebben.

Laten we beginnen met een lijst met relationele operatoren:

operator Beschrijving MEESTE basismonster
> Groter dan ? 1> 2 &&F.
< Minder dan ? 1 <2 && .T.
> = Groter dan of gelijk aan ? 1> = 2 && .F.
<= Minder dan of gelijk aan ? 1 <= 2 && .T.
= Gelijk aan ? 1 = 1 && .T.
== Is precies gelijk aan (is logisch voor tekenreeksen) ? '1' = '1' && .T.
! =, #, <> Niet gelijk aan (alle 3 operators werken op dezelfde manier, kies je favoriet) ? 1! = 1 && .F.

Hoewel u deze met alle gegevenstypen kunt gebruiken, moet er een typecompatibiliteit tussen de operanden zijn. U krijgt bijvoorbeeld een foutmelding als u probeert een datum te vergelijken met een geheel getal.

Datum en Datetime kunnen worden vergeleken, hoewel ze verschillende typen zijn, voert VFP impliciet de conversie voor u uit.

? Date() > DateTime() && .F. 
? Date() <= DateTime() && .T. 
? Date() < DateTime() && .T. if it is not midnight

Wanneer de operanden numeriek zijn, zijn al deze operatoren eenvoudig en ongecompliceerd, ze werken zoals ze zouden doen in wiskundige uitdrukking.

Met de logische operanden, F. wordt beschouwd als minder dan .T.

Met objecten vergelijken we de referentie van het object in het geheugen. De meest gebruikte vergelijking is dus om te bepalen of twee objectvariabelen naar hetzelfde object wijzen. d.w.z:

local o1, o2
o1 = createobject('Label')
o2 = createobject('Label')
? m.o1 = m.o2 && is o1 and o2 the same object?
? m.o1 > m.o2 && this would work too but likely you would never use

* remember we are comparing their references in memory
* 
* They are different objects, but do they have any difference in their properties?
? CompObj(m.o1, m.o2) && .T. They are identical properties wise

Vergelijking van het karaktergegevenstype, aka vergelijking van strings is de meest verwarrende in VFP. Het werkt niet zoals in andere talen en / of databases en uniek voor VFP (en misschien voor een andere xBase-taal).

Vele jaren geleden heb ik zelfs een aantal zeer geavanceerde leden in de community gezien die nog niet wisten hoe deze operators werken in VFP. Het is dus heel begrijpelijk dat kleine nuances de nieuwelingen gemakkelijk kunnen verwarren.

Vergelijking gaat eigenlijk over gelijk zijn of niet. Als ze niet gelijk zijn, kunnen we denken aan de operatoren>, <,> =, <=, toch? Met strings is het verwarrend wanneer twee strings als gelijk worden beschouwd .

Belangrijk: VFP-reeksen zijn hoofdlettergevoelig. 'A' en 'a' zijn twee verschillende tekenreeksen. Dit is niet het geval met veel databases waar standaard een niet-hoofdlettergevoelige sortering wordt gebruikt. Bijvoorbeeld in postgreSQL of MS SQL Server op een tabel gemaakt met hoofdlettergevoelige (CI) sortering:

select * from myTable where Country = 'TURKEY'

select * from myTable where Country = 'Turkey'

zou hetzelfde resultaat opleveren. In VFP krijg je echter alleen die waar behuizing overeenkomt. VFP heeft echter enige sorteerondersteuning en maakt hoofdletterongevoelige vergelijking. (Vertrouw niet, zie hieronder)

  • Als twee tekenreeksen niet gelijk zijn, tot nu toe zo goed, op voorwaarde dat u geen standaardwaarden hebt gewijzigd, worden ze vergeleken op basis van hun ASCII-waarden .

    ? 'Basoz' < 'Cetin' && is true.
    ? 'basoz' < 'Cetin' && is false.
    ? 'Cetin' < 'David' && is true.
    ? 'Çetin' < 'David' && is false.
    

De standaardinstelling voor sorteren is 'machine' en dit is wat u dan krijgt. Wanneer u de sortering in iets anders verandert, krijgt u de vergelijking op basis van de sorteervolgorde van die sortering. Met collatie andere instelling dan standaard machine die u ook wat een geval ongevoeligheid op een vergelijking (doe het niet om gelijkheid vertrouwen):

  set collate to 'GENERAL'
  ? 'Basoz' < 'Cetin'
  ? 'basoz' < 'Cetin'
  ? 'Cetin' < 'David'
  ? 'Çetin' < 'David'

Nu zijn al deze uitdrukkingen WAAR.

Persoonlijk advies: Collaties in VFP zijn nooit betrouwbaar genoeg geweest. Ik stel voor dat u geen collaties gebruikt en standaard 'MACHINE' gebruikt. Als u sorteringen zou gebruiken, houd er dan rekening mee om dit eerst te controleren wanneer u iets ervaart dat zeer onverwacht is met betrekking tot karaktergegevens. Ik heb gezien en aangetoond dat het in veel gevallen mislukt, maar toen stopte ik ermee het veel vóór de VFP9-versie te gebruiken, het kan nu consistent zijn, ik weet het echt niet.

Aangezien we ongelijkheidsgevallen met strings hebben behandeld, is het lastige geval het geval van gelijkheid. In VFP hebben in principe twee instellingen effect op de vergelijking:

  1. EXACTE INSTELLEN (Standaard is UIT en bewerkstelligt regelmatige vergelijkingen - behalve SQL)
  2. SET ANSI (standaard is UIT en heeft alleen effect op vergelijkingen in SQL. SET EXACT heeft geen effect op vergelijkingen die binnen SQL-query's worden gemaakt.

Met SET EXACT OFF, lees de vergelijking als "begint string rechts met de string links"? Ze worden vergeleken tot de lengte van de juiste snaar.

? "Bobby" = "B" && Bobby starts with B, so TRUE
? "Bobby" = "Bob" && Bobby starts with Bob, so TRUE
? "Bobby" = "Bob " && Bobby starts with Bob but there is a trailing space there, FALSE
? "Bobby" = "bob" && would be true with collation set to GENERAL

Merk op dat met regelmatige vergelijking "Bobby" = "B" WAAR is, maar "B" = "Bobby" is ONWAAR. Met andere woorden, de plaats van operanden is belangrijk.

Met SET EXACT ON moeten de strings volledig overeenkomen, maar hun volgspaties worden genegeerd (we negeren set collate hier, wat ook hoofdletterongevoeligheid zou doen):

? "BOBBY" = "BOB" && FALSE 
? "BOBBY" = "BOBBY" && TRUE
? "BOBBY" = "BOBBY     " && TRUE 
? "BOBBY     " = "BOBBY" && TRUE

Nu, met SQL-opdrachten heeft SET EXACT geen effect en zou het zich gedragen zoals SET EXACT OFF doet.

Select * from Customers where Country = 'U'

Zou de klanten uit de VS, VK elk land selecteren dat begint met 'U'.

In SQL moet echter per definitie het wijzigen van de volgorde van operanden hetzelfde resultaat opleveren. Dus:

Select * from Customers where 'U' = Country

zou ook op dezelfde manier werken (let op het verschil met niet-SQL-opdrachten).

Als u exacte overeenkomsten wilt impliceren, kunt u ANSI inschakelen:

SET ANSI ON
Select * from Customers where Country = 'USA'

retourneert al die klanten uit de VS. Merk op dat de volgspaties in het landveld OF op de juiste uitdrukking worden genegeerd. Het maakt niet uit hoeveel je aan beide kanten hebt achtergelaten. Je krijgt de vergelijking alsof het gedaan is zoals: RTRIM (Land) = RTRIM ('USA').

Hoewel het niet wordt vermeld in Operators in VFP, is een SQL- operator LIKE. Wanneer u LIKE gebruikt, krijgt u een exacte vergelijkingsvergelijking ongeacht de instelling ANSI-instelling (met behulp van LIKE-krachten en impliciet ANSI ON-geval - het is tenslotte een ANSI-operator). Let echter op dat er een klein verschil in gedrag is. Het zou geen volgspaties negeren, tenzij de totale grootte met aanhangwagens gelijk is aan of kleiner is dan de veldgrootte. Als het veld Land bijvoorbeeld C (10) is, zou Country = 'USA' of Country = 'USA__' werken, maar Country = 'USA___________' zou mislukken ( onderstrepingstekens geven een spatie aan en de laatste heeft meer dan 7 volgspaties).

Eindelijk zijn we tot de laatste operator, ==. Dat betekent precies gelijk en maakt te gebruiken met tekenreeksen. Een voordeel is dat u met == altijd bedoelt dat u exact wilt zoeken, ongeacht de instellingen van SET EXACT of SET ANSI. Pas echter nogmaals op, het gedrag is anders wanneer het een SQL-opdracht of een niet-SQL reguliere opdracht is.

Met SQL:

Select * from Customers where Country == 'USA'

wat de ANSI- en EXACT-instellingen ook zijn, we willen alleen alle klanten uit de VS. Trailing-spaties aan beide zijden worden genegeerd.

Met niet-SQL:

? m.lcString1 == m.lcString2

zou alleen waar zijn als ze exact hetzelfde zijn, met betrekking tot hun behuizing en lengte (volgspaties worden NIET genegeerd). Het wordt niet beïnvloed door de instellingen SET ANSI, EXACT of COLLATE.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow