WORDS
Introduktion till Linq: Funktionell programmering och lat evaluering
Linq (Language Integrated Query) är en komponent i C# och .Net. I det här inlägget tittar vi på hur Linq genom sin query-funktionalitet hjälper oss att uttrycka programmeringsproblem mer deklarativt i enlighet med principerna för funktionell programmering, i kontrast till imperativ programmering.
Puh, det var en samling konstiga ord. Men det är inte så komplicerat egentligen. Linq hjälper oss med allt det ovanstående på åtminstone två sätt: inte modifiera objekt, och så beskriver vi vad vi vill åstadkomma, men inte exakt i vilken ordning.
Oföränderliga listor
Med Linq tar man något enumererbart (IEnumerable
), och beskriver vilka funktioner man vill applicera på samlingen. Alla Linq-metoder låter den ursprungliga samlingen vara i oförändrat skick, och skapar istället en ny IEnumerable
.
var myList = new List<int> { 1, 2, 3, 4, 5 };
// `myList` kommer inte ändras.
var newList = myList.Select(x => x * 2).ToList();
En av principerna inom funktionell programmering är just det att inte mutera något, vare sig listor eller andra objekt. Med Linq blir det alltså enklare att arbeta med listor utan att ändra dom.
Lat evaluering
Lat evaluering (eng. lazy evaluation) innebär att man väntar med att beräkna värden förrän det behövs. Med Linq så görs inga beräkningar förrän någon ber om dom.
Det blir tydligare med ett exempel.
/// <summary>
/// Returnerar `true` om samlingen innehåller fler än 50
/// värden under tio.
/// </summary>
public bool HasManySmall(IEnumerable<int> numbers)
{
// Ingenting händer ännu.
var smallNumbers = numbers.Where(x => x < 10);
// Nu behöver vi veta något om antalet, så nu anropas
// kodraden ovanför.
var hasManySmall = smallNumbers.Count() > 50;
return hasManySmall;
}
Samma sak igen, men med Console.WriteLine()
.
var numbers = new List<int> { 1, 2, 3 };
Console.WriteLine("Initial log");
var smallNumbers = numbers.Where(x =>
{
Console.WriteLine($"Inside Where: {x}");
return x < 10;
});
Console.WriteLine("Before Count");
var hasManySmall = smallNumbers.Count() > 50;
Console.WriteLine("After Count");
/*
* Logg från denna kod:
* Initial log
* Before Count
* Inside Where: 1
* Inside Where: 2
* Inside Where: 3
* After Count
*/
Observera att Before Count
kommer före Inside Where: x
. Det var alltså först vid Count()
som vi behövde gå igenom samlingen.
Och åter samma sak, men den här gången lägger vi till en ToList()
.
var numbers = new List<int> { 1, 2, 3 };
Console.WriteLine("Initial log");
var smallNumbers = numbers
.Where(x =>
{
Console.WriteLine($"Inside Where: {x}");
return x < 10;
})
.ToList();
Console.WriteLine("Before Count");
var hasManySmall = smallNumbers.Count() > 50;
Console.WriteLine("After Count");
/*
* Logg från denna kod:
* Initial log
* Inside Where: 1
* Inside Where: 2
* Inside Where: 3
* Before Count
* After Count
*/
Här kommer Before Count
efter Inside Where: x
. För att kunna skapa en riktig List
vid ToList()
, inte bara en IEnumerable
som man kan räkna upp, så måste man faktiskt gå igenom alltihop.
Metoder som inte är lata
Vi har sett att Count()
och ToList()
gör att allt evalueras, medan Where()
inte evalueras förrän det krävs. Här är en lista över dom metoder som gör att allt evalueras:
Count()
ToList()
ToArray()
ToDictionary()
Aggregate()
Följande metoder evaluerar så mycket som krävs, vilket kan vara en enstaka iteration, eller hela samlingen:
.First()
.FirstOrDefault()
.Single()
.SingleOrDefault()
.Any()
.All()