Vilket år haskel dök upp. "Och vem gjorde det?" Funktioner hos funktionella språk

Syntaxen för två på varandra följande identifierare innebär att foo-funktionen tillämpas på dess bar-argument:

I Haskell kräver anrop av en funktion inte parenteser runt argumentet.

Parenteser används för att gruppera argument:

acos (cos pi)

Multi-argument funktion:

max 5 42

Funktionen tillämpa operationen lämnas associativ:

(max 5) 42

Fungera max tillämpas sekventiellt på två argument.
Kompilatorn förstår konstruktionen f x y hur (fx)y, och inte vice versa f(xy).

Uttryck (max 5) detta är den så kallade delfunktionsapplikationen. I allmän syn det kan formuleras på följande sätt: om vi har en funktion av N variabler och vi ser på det som en funktion av N variabler, så kan vi titta på det från andra sidan och säga att det är en funktion av att en variabel återvänder till oss en funktion av N - 1 variabler.

3+sin42

3 + (max 5) 42

Anpassad funktionsdeklarationssyntax

En funktion som summerar kvadraterna av de två argument som skickas till den:

SumSquares x y = x ^ 2 + y ^ 2 rock"n"roll = 42


Funktionsnamnet och formella parameternamn måste börja med gemener. Versaler används för att definiera datatyper.

En funktion av tre argument som beräknar längden på en 3D-vektor:

LenVec3 x y z = sqrt(x^2 + y^2 + z^2 )


För att definiera en funktion i GHCi-tolken måste vi använda nyckelordet let.

Låt summakvadrater x y = x^2 + y^2

Renhetsegenskap för en funktion

En viktig egenskap som skiljer funktionella språk från imperativa språk är egenskapen funktionsrenhet. Alla funktioner i Haskell är rena. Detta betyder att värdet på en funktion helt bestäms av värdena för argumenten som skickas till den. Inga andra datakällor kan påverka resultatet som returneras av funktionen. Om du vill att funktionen ska ta data från någonstans, måste du skicka denna datakälla till den som ett argument.

En funktion som inte tar några argument är en konstant. En sådan funktion returnerar alltid samma värde, oavsett omständigheter.

Preludium > låt fortyTwo = 39 + 3 Prelude > fortyTwo 42

I Haskell kan du inte definiera en funktion som inte tar några argument och returnerar olika värden på olika anrop.

Funktionsdefinitionsmekanism med partiell tillämpning

Vi kan se vilken funktion som helst som en funktion av ett argument som returnerar någon funktion.

Prelude > låt max5 x = max 5 x Prelude > max5 4 5 Prelude > max5 42 42


Alternativ funktionsdefinitionssyntax:

Preludium > låt max5" = max 5 Prelude > max5" 4 5 Prelude > max5" 42 42

Vi har trunkerat det extra argumentet x till vänster och höger. Och de skrev att funktionen max5" bara är en delvis tillämpad funktion max. På så sätt kan du definiera en funktion utan att specificera alla argument. Motsvarande programmeringsstil kallas dotless.

Ofta anpassas utformningen av funktioner i Haskell så att partiell applicering är bekvämt;

Prelude > låt rabatt begränsa proc summa = om summa >= limit then summa * (100 - proc) / 100 else summa Prelude > let standardRabatt = rabatt 1000 5 Prelude > standardRabatt 2000 1900.0 Prelude > standardRabatt 900 900,0

Gräns- och proc-parametrarna ändras sällan. Men summaparametern ändras ofta. Faktum är att varje gång den här funktionen anropas.

Anta att vi utvecklar ett gränssnitt för ett översättningssystem för naturliga språk i Haskell. Den måste innehålla en översättningsfunktion med parametrarna text, languageFrom och languageTo. För att göra det bekvämt att implementera följande funktioner:

  • översätt från spanska till ryska,
  • översätt från engelska till ryska
  • och översätt till ryska
du måste ordna parametrarna i denna ordning: översätt språkTill språkFrån text.

Operatör över typer ->

För att skriva typen av en funktion måste du skriva typen av dess argument och typen av resultatet av denna funktion. I Haskell används operatorn -> för att beskriva typen av en funktion, som är en binär operator vars vänstra operand är argumentets typ och den högra operanden är resultatets typ. Pilen är mellan vänster och höger operander eftersom det är en infixoperator.

Prelude > : t not not :: Bool -> Bool Prelude > (&& ) False True False Prelude > ((&& ) False ) True False

Typen av det sista uttrycket kan skrivas på följande sätt:
Bool -> (Bool -> Bool)
Typoperatorn anses vara högerassociativ. Därför kan Bool -> (Bool -> Bool) skrivas om till Bool -> Bool -> Bool

Preludium > : t (&& ) (&& ) :: Bool -> Bool -> Bool

Återkalla rabattfunktionen, som återkom totala summan handla med rabatt. Som parametrar överfördes beloppet utan rabattsumman, procentandelen av rabattprocen till den, och rabatten beräknades om det överförda beloppet översteg gränsvärdet. Alla dessa parametrar, såväl som returvärdet, kan lagras i typen Dubbel. Funktionstypen kan anges i källkodsfilen tillsammans med dess definition:

rabatt :: Dubbel -> Dubbel -> Dubbel -> Dubbel rabattgräns proc summa = om summa >= gräns sedan summa * (100 - proc) / 100 annat summa

Observera att typdeklarationen är valfri, även om den ofta rekommenderas som dokumentation. Den placeras vanligtvis före funktionsdefinitionen, även om denna deklaration högsta nivån kan placeras var som helst i källfilen.

Standardrabatt :: Dubbel -> Dubbel standardrabatt = rabatt 1000 5

Tänk på funktionen twoDigits2Int, som tar två tecken och returnerar ett tal som består av dessa tecken om båda tecknen är numeriska och 100 i annat. (Det första tecknet behandlas som ett antal tior, det andra tecknet behandlas som enheter.)

Importera Data.Char twoDigits2Int :: Char -> Char -> Int twoDigits2Int x y = if isDigit x && isDigit y then digitToInt x * 10 + digitToInt y else 100


GHCi > twoDigits2Int "4" "2" 42

rekursion

I imperativa språk är huvudelementet i upprepade beräkningar en loop. Slingor är inte mycket meningsfulla i funktionella språk. Eftersom det inte finns något koncept för en föränderlig variabel i funktionella språk, finns det inget sätt att skilja en iteration av en loop från en annan. Rekursion används i funktionella språk för att utföra repetitiva beräkningar. För att en rekursiv funktion inte ska gå i loop måste den uppfylla följande krav:
  • funktionsanrop på höger sida måste utföras på andra parametervärden än formell parameter funktioner;
  • rekursiva samtal måste bryta någonstans (det måste finnas ett så kallat terminating condition).

Faktoriell

Faktoriell n = om n == 0 så 1 annan n * faktoriell (n - 1 )


Implementering av faktorberäkningen med den ackumulerande parametern:

Faktoriell 5 n | n >= 0 = hjälpare 1 n | annars = fel "arg måste vara >= 0" helper acc 0 = acc helper acc n = helper (acc * n) (n - 1 )

Den här typen av implementering i fallet med factorial ger inga ytterligare fördelar, men mycket ofta kan sådana implementeringar förbättra effektiviteten hos rekursiva funktioner. I många fall, i rekursiva funktioner, genom att ge en direkt definition av den rekursiva funktionen i termer av sig själv, kan man få en kvadratisk eller högre polynomasymptotik. Och i fallet med hjälpfunktioner är det mycket ofta möjligt att korrigera asymptotiken genom att reducera den till en linjär.

dubbelfaktorial

Betrakta en funktion som beräknar dubbelfaktorialen, det vill säga produkten av naturliga tal som inte överstiger ett givet tal och har samma paritet. Till exempel: 7!!=7⋅5⋅3⋅1, 8!!=8⋅6⋅4⋅2. Det antas att funktionsargumentet endast kan ta icke-negativa värden.

DoubleFact :: Heltal -> Heltal doubleFact n = om n<= 0 then 1 else n * doubleFact (n - 2 )

Fibonacci nummersekvens

I Haskell ges denna definition av följande funktion:

Fibonacci :: Heltal -> Heltal fibonacci n | n == 0 = 0 | n == 1 = 1 | n > 1 = fibonacci (n-1) + fibonacci (n-2) | n< 0 = fibonacci (n + 2 ) - fibonacci (n + 1 ) | otherwise = undefined

Implementeringen av funktionen för att beräkna Fibonacci-talet, baserat på en direkt rekursiv definition, är extremt ineffektiv - antalet funktionsanrop växer exponentiellt med ökningen av argumentvärdet. GHCi låter dig övervaka minnesanvändning och den tid det tar att utvärdera ett uttryck genom att köra kommandot :set +s:

* Fibonacci > : set + s * Fibonacci > fibonacci 30 832040 (16,78 sekunder, 409 318 904 byte)

Med hjälp av ackumulatormekanismen kan du skriva en mer effektiv implementering som har linjär komplexitet (när det gäller antalet rekursiva anrop):

Fibonacci" :: Heltal -> Heltal fibonacci" n = hjälpare n 0 1 hjälpare n a b | n == 0 = a | n > 0 = hjälpare (n - 1) b (a + b) | n< 0 = helper (n + 1 ) b (a - b) | otherwise = undefined


Funktioner av högre ordning

En högre ordningsfunktion är en funktion som tar en annan funktion som argument. Funktioner av högre ordning är sällsynta i imperativa språk. Ett exempel är sorteringsfunktionen från C-standardbiblioteket. Som det första argumentet skickas ett binärt jämförelsepredikat till sorteringsfunktionen, med hjälp av vilken arrayen, som skickas som det andra argumentet, sorteras. på funktionella språk och Haskell funktioner högre order används mycket ofta.

Preludium > : t ($ ) ($ ) :: (a -> b) -> a -> b

Detta är en polymorf typ. Dollaroperatorn är en funktion av två argument. Dess vänstra operand eller första argument (a -> b) är en funktion. Dess högra operand a är ett värde av en godtycklig typ. Dollaroperatorn tillämpar helt enkelt sitt första argument (a -> b) på sitt andra argument a . Därför är det nödvändigt här att typerna är konsekventa. Typen av det andra argumentet för dollaroperatorn måste matcha typen av parameter för funktionen som skickas i det första argumentet. Dessutom är resultatet av dollaroperatorn resultatet av funktionen som skickas som det första argumentet. Eftersom resultattypen är b, är resultatet av dollaroperatorn typ b.

Följande ansökan är endast giltig när a och b är samma.

Prelude > let apply2 fx = f (fx) Prelude > : t apply2 apply2 :: (t -> t) -> t -> t Prelude > apply2 (+ 5 ) 22 32 Prelude > apply2 (++ "AB" ) " CD" "CDABAB"

Det första argumentet är verkligen en funktion, men argumentet och returvärdet är av samma typ. Och det andra argumentet är värdet av samma typ, och returvärdet är också värdet av denna typ. Således kan applicera2-funktionen tillämpas på en mer begränsad uppsättning funktioner än dollarn. Om dollar är polymorf i argument och returvärde, är applicera2 polymorf i endast en parameter.

Vändfunktionen från standardbiblioteket definieras enligt följande: flip f y x = f x y.

Prelude > flip (/ ) 4 2 0.5 Prelude > (/ ) 4 2 2.0 Prelude > flip const 5 True True Prelude > : t flip flip :: (a -> b -> c) -> b -> a -> c Preludium > : t flip const flip const :: b -> c -> c

(- En användbar funktion är definierad i Data.Function-modulen högre ordning -} på :: (b -> b -> c) -> (a -> b) -> a -> a -> c på op f x y = f x `op` f y (- Det krävs fyra argument: 1) binär operator med argument av samma typ (typ b), 2) funktion f:: a -> b, returnerar ett värde av typ b, 3.4) och två värden av typ a. På-funktionen tillämpar f två gånger på två värden av typ a och skickar resultatet till en binär operator. Med on kan du till exempel skriva funktionen att summera kvadraterna på argumenten så här:-) sumSquares = (+ ) `på` (^ 2 ) (- MultiSecond-funktionen, som multiplicerar de andra elementen i paren, implementeras enligt följande -) multSecond = g `på` h g = (* ) h = snd

Anonyma funktioner

I Haskell, liksom i matematik, brukar funktioner benämnas. När vi behöver anropa en funktion hänvisar vi till den med dess namn. Det finns dock ett alternativt tillvägagångssätt som kallas anonyma funktioner.

Preludium > (\ x -> 2 * x + 7 ) 10 27 Prelude > let f" = (\ x -> 2 * x + 7 ) Prelude > f" 10 27

Detta anonym funktion eller lambdafunktion.

Det finns syntaktisk socker för att förenkla notationen.

Prelude > let lenVec xy = sqrt $ x^ 2 + y^ 2 Prelude > let lenVec x = \ y -> sqrt $ x^ 2 + y^ 2 Prelude > let lenVec = \ x -> \ y -> sqrt $ x ^2 + y^2 Prelude > lenVec 3 4 5.0 Prelude > let lenVec = \xy -> sqrt $x^2 + y^2 Prelude > lenVec 3 4 5.0


Anonyma funktioner används vid användning av funktioner av högre ordning.

(- Funktion on3, har semantik liknande på, men tar en ternär funktion som första argument -) on3 :: (b -> b -> b -> c) -> (a -> b) -> a -> a -> a -> c on3 op f x y z = op (f x) (f y) (f z) (- Summan av kvadraterna av tre tal kan skrivas med on3 så här -) sum3squares = (\ x y z -> x+ y+ z) `on3` (^ 2 )

Curried och Uncurried funktioner

Med Haskels funktionsanropssyntax är det möjligt att lista inte alla argument, utan bara en del av dem. De första argumenten till en funktion kan specificeras och resten kasseras. Denna idé om partiell tillämpning av funktioner myntades av Haskell Curry, och efter honom kallas sådana funktioner med argument som passerats en i taget för curry. I Haskell är inte alla funktioner curry. Haskell låter dig definiera funktioner på tupler. I det här fallet kommer syntaxen för anropsfunktioner att se ut som på vanliga språk:
funktionsnamn (första_argument, andra_argument)

Preludium > fst (1 ,2 ) 1


Currying är proceduren för att gå från icke-curry-funktioner till funktioner som tar argument ett i taget. Föreställ dig att vi har en funktion av högre ordning, som på kombinatorn, den förväntar sig som två av sina fyra funktionsargument. Det första argumentet är en funktion av två argument som är curry. Haskell har en speciell curry-kombinator som utför övergången från en okurrig funktion till en curry-funktion. I följande exempel förvandlar curry en funktion som ges över ett par till en standard curryfunktion med två argument.

* Demo > : t on on :: (b -> b -> c) -> (a -> b) -> a -> a -> c * Demo > : t curry fst `on` (^ 2 ) curry fst `on` (^ 2 ) :: Num b => b -> b -> b


Ett annat exempel, en okränkt medelfunktion:

Avg :: (Dubbel ,Dubbel) -> Dubbel medelp = (fst p + snd p) / 2

Funktionen curry avg `on` (^2) är en funktion som beräknar medelvärdet av kvadraterna av två värden som skickas till den.

Curryfunktionsenhet:

Preludium > let cur fxy = f (x,y) Prelude > : t cur cur :: ((t1, t2) -> t) -> t1 -> t2 -> t Prelude > : t curry curry :: ((a) , b) -> c) -> a -> b -> c

Den tar en oklar funktion som sitt första argument, dvs. funktion över ett par, men omvandlar det som ett returvärde till en curried funktion till vilken argumenten skickas sekventiellt.

Det finns också invers funktion oklar:

Preludium > : t uncurry uncurry :: (a -> Prelude > : t uncurry (flip const) uncurry (flip const) :: (b, c) -> c Prelude > : t snd snd :: (a, b) - >b

Data.Tuple-modulen i standardbiblioteket definierar funktionen swap:: (a,b) -> (b,a), som byter ut elementen i ett par:

GHCi > swap (1 ,"A") ("A" ,1 )

Denna funktion kan uttryckas som:

Preludium > låt byta = oklar (vända (,)) Preludium > byta (1 ,"A" ) ("A" ,1 )

Strikta och icke-strikta funktioner

Lat utvärdering leder till det faktum att det i många situationer är möjligt att eliminera att program inte kan avslutas.

const42 :: a -> int const42 = const 42

Const42-funktionen ignorerar helt argumentets värde, så inom ramen för den lata beräkningsmodellen, om någon komplex beräkning skickas till den som ett argument, kommer denna beräkning aldrig att ske. Och detta betyder att vilket program som helst, inklusive ett icke-avslutande program, kan skickas som ett argument till const42.

* Demo > const42 True 42 * Demo > const42 123 42 * Demo > const42 (1 + 3 ) 42 * Demo > const42 undefined 42

Funktioner som const42 kallas icke-strikta funktioner. Om en divergerande beräkning skickas till en funktion som ett argument, och resultatet är något icke-divergent värde, kallas en sådan funktion icke-strikt. En funktion kallas strikt så att om vi skickar ett divergent argument till den, så är värdet av denna funktion nödvändigtvis divergent. En funktion av två argument kan vara strikt eller icke-strikt med avseende på det andra argumentet, beroende på värdet av dess första argument. Styvhetsanalys är avgörande för att göra ett Haskell-program mycket effektivt.

Jag tror att alla vanliga läsare av min blogg vet att jag är en måttligt ivrig följare. Det är inte så att jag inte vill höra något om allt annat - nej, jag står bara alltid upp för val, diskussion och konstant sökande det bästa, och därför i slutändan, för en viss utveckling av utvecklingsmetoder. Personkulten är lika obehaglig för mig som kulten av en separat programmeringsmetodik, eftersom det i längden är en tydlig väg till stagnation och galenskap.

För att fortsätta detta pedagogiska arbete vill jag idag fokusera på ett underbart funktionellt programmeringsspråk. Jag har redan fått samma fråga tre gånger: hur börjar jag (fortsätter) lära mig Haskell?

Det kanske är dags att ge ett kort svar-råd. Här allmän algoritm inträde i detta ämne från mig.

Steg 0 - Introduktion. Haskell? Vad i helvete?

En välkänd paradox bland rekryterare av programmerare, ofta kallad "", och den är formulerad ungefär så här:

Om ett företag väljer något obskyrt, esoteriskt programmeringsspråk som sitt primära programmeringsspråk, då har det företaget störst chans att få de bästa programmerarna på marknaden. Varför? Faktum är att för det första kommer de programmerare för vilka att lära sig nya saker inte är ett problem att vilja anställas i ett sådant "konstigt företag"; de för vilka de föga kända och svåråtkomliga inte är ett hinder; och slutligen de som har tillräckligt hög självkänsla för att erbjuda sig själva under så uppenbart svåra förhållanden.

Och viktigast av allt, det finns två typer av programmerare: de som studerar för att få Bra jobbat och de väljer alltid vanliga eftersom det avsevärt ökar deras chanser till anställning; och de som bara gillar att lära sig något nytt, utvecklas och de väljer alltid Det bästa, vilket ofta är långt ifrån den mest lönsamma som deras medkarriärister gör. Så, Python paradox hävdar att börjar utvecklingen på det avancerade exotiska, som en dammsugare, kommer du att locka den andra kategorin av programmerare (det omvända är också sant om företag som erbjuder arbete).

Jag kan ge som ett abstrakt exempel på en helt liknande hemlig inriktning av fokusgrupper med givna egenskaper, en berättelse från hans senaste ungdom. När jag fortfarande studerade hade vi en "konstig" undertext, som trotsigt när den presenterades matematisk analys aldrig uppmärksammat höger sida publik. Det vill säga, det var två rader i publiken - vänster och höger - och nu håller han en föreläsning, förklarar något, men samtidigt tittar han ALDRIG på högerraden - all uppmärksamhet är bara på eleverna från vänster raden . Också med svar på frågor - den högra raden fanns inte för honom. Därifrån hör han ingenting.

Som ett resultat, under en lång tidsperiod, märkte jag en intressant sak: i vår grupp fanns det en automatisk självidentifiering och fragmentering i de som matematisk analys behövde och intressanta (och de tog omedelbart plats på den vänstra raden efter att ha accepterat reglerna för detta konstiga spel), och de som inte behövde det, och de hade bråttom att ta plats till höger, för här var de lämnade åt sig själva. Denna sortering skedde av sig själv, utan någon extra extraordinär ansträngning från sidan. För läraren, som jag förstod hans motivation, var det mycket bekvämt - han slösade inte bort sin styrka på utomstående i huvudsak, utan skapade omedelbart förutsättningar för att koncentrera sin styrka och uppmärksamhet på dem som behövde hans kunskap. För C-elever var detta också en win-win-situation.

Steg 3 - Att hitta djupet och känslan av ett nytt språk

Den tredje etappen gräver redan djupt. Här föreslår jag att bryta de traditionella mönstren lite (och jag älskar att göra detta) och byta till ett fundamentalt annorlunda format för att presentera information: detta kommer inte bara att diversifiera uppgiften att lära sig språket, utan också involvera nya, hittills inte aktiverade områden av din hjärna (vi kommer speciellt att anstränga din stulna programmerares vänstra hjärnhalva). Jag menar en bra videoföreläsning om Haskell från en väldigt smart kille med engelska rötter.

Här är hennes output:

Kursen Funktionell programmering med Haskell
(Språket engelska)
35 timmar | 1280x720 | Xvid - 1326 Kbps
25.00fps | Mp3 - 96Kbps | 20,06 GB

Du kan ladda ner denna fantastiska videoföreläsning. Jag råder dig att skynda dig, tills de indignerade upphovsrättsinnehavarna sprang iväg och spikade denna sällsynta arkivnafig ( uppdatering: ja, de spikade honom: amen. Jag föreslår alla intresserade att använda denna mega-länk).

4. Det sista steget - övning

Sökandet efter den mest lämpliga och inspirerande praktiska uppgiften för dig är ämnet för ett separat inlägg, där jag skulle vilja ge några levande exempel från berömdas liv goner programmerare, så jag kommer tills vidare att skjuta upp mina förklaringar om detta sista stegetåt sidan ett tag.

Här, något sånt här, delvis självklart för många, ville jag ge en algoritm till alla som vill lära sig Haskell, och inte bara. Våga!

Och sammanfattningsvis, för anhängare av andra programmeringsspråk:

Haskell är ett heligt programmeringsspråk som ges till shamaner Bubenland deras högsta gudom Komonad hur universellt botemedel för kommunikation och andlig rening, lämplig för både gudomliga varelser och (vissa) enbart dödliga som har varit sjuka med svåra stadier av intellektet. På grund av sitt ursprung har språket alltid varit funktionellt rent. I genomsnitt börjar lära sig Haskell vid 10-12 år. Att börja din träning i tid kommer att säkerställa att du når den tredje nivån av Force vid 75 års ålder. Skjut inte upp det du kan till nästa liv minst börja i denna.

Matthew Griffin

Efter att ha studerat Haskell under en längre tid har jag fått tillräckligt med erfarenhet för att ge dig ett par tips nu. Jag skulle också vilja lära mig några principer för mig själv innan jag går vidare.

Och även om jag ibland tar till Python hjälp, jag gör nu det mesta av mitt arbete på webben i Haskell.

Först och främst data

Jag funderade på att gå från ett dynamiskt till ett statiskt språk, och i Haskell är strukturen för den data du arbetar med tydligt definierad när du deklarerar den. I Python görs den här uppgiften för det mesta av koden.

När jag först såg funktioner i Haskell undrade jag: "Vad är data? Tar den här funktionen något som input och producerar något som output? Och när jag arbetade med Python hade jag en fråga: "VAD SÄGER KODEN?"

Haskell-datastrukturen bildade ett kvalitativt nytt sätt att tänka för mig, som jag tog med mig till mitt arbete i Python. Min kod är märkbart bättre. Och även om det väldigt ofta verkade för mig att formen på de uppgifter jag beskrev förändrades utan anledning, men i själva verket blev allt klart med en liten studie av frågan. Inskränkningar i friheten att modifiera data gör också koden mindre komplex och mer begriplig.

Kodens läsbarhet

Python lockade mig med förmågan att verkligen skriva läsbar kod. Haskell-kodexemplen såg hemska ut, förutom några utdrag som verkade vara valda med flit för att inte skrämma nybörjare. Och även om vissa bitar av kod såg väldigt snygga ut, var det mesta av källkoden fylld med något hemskt. Och bakom detta "hemska" låg just språkets hela kraft.

Jag ville definitivt inte skriva "smart" kod - en som kan demonstrera språkets imponerande egenskaper, men som är helt obekväm.

Jag har dock utvärderat läsbarheten hos Haskell i jämförelse med andra populära programmeringsspråk. Det var som om jag utvärderade kinesiska att vara engelska som modersmål.

Jag insåg att Haskell inte är ett "smart" språk, utan med ett trick. Visst går det att skriva "smart" Haskell-kod, men det är inget vanligt fall. Samtidigt begränsas kraften i smart kod av stark skrivning. Om en funktion skulle returnera en Int, kommer den att returnera en Int. Tja, eller kasta ett kompileringsfel som en sista utväg.

Och de mer kraftfulla abstraktionerna i Haskell är som en del av magin som jag försökte undvika när jag arbetade med Python.

Jag menar allvar med läsbarhet

Först, tro och övertyga dig själv att, ja, folk läser listor på detta nya språk åt dig, och de gör det snabbt och regelbundet. Efter att äntligen ha listat ut de flesta av de kända nyanserna började jag känna mig mer avslappnad inför Haskell-koden.

Kommentarer. De tar översta raden i ett av kapitlen i vår "bok".

Det här kapitlet beskriver hur Tommy gick till affären och köpte en anka.

kapitel:: Tommy -> Marknad -> Anka

Funktioner från en annan, reducerad funktion, i det stora hela reducerar de koden maximalt.

korthet. Du behöver inte massor av kod för att implementera dina idéer.

Infoga symboler

Jag ville också nämna insättningsfunktioner, som är vanliga i Haskell. Infogningsfunktioner (eller operatorer) är de funktioner som använder mellan för två argument istället för innan. Ett enkelt exempel är "+".

I Haskell har vi några infogningstecken som används som standard: $,<$>, <-, ->, etc. De kan förvirra dig lite i början av resan.

Oroa dig inte! När du har lärt dig hur du använder dem kommer du att inse hur användbara och enkla de är. Jag räknade ungefär 5 tecken som jag använder regelbundet.

Jag rekommenderar inte att du använder linsbiblioteket direkt, eftersom det innehåller massor av sådana symboler. Detta bibliotek är mycket användbart, men till en början kommer du att kunna nå stor framgång utan det. Vänta tills du bekvämt kan skriva måttligt komplexa program i Haskell och börja sedan använda lens.

Behöver uppdatera kunskap

När du lär dig Haskell kommer du att stöta på nya termer på vägen, som t.ex funktör eller monad.

Du kanske tror att dessa ord är svåra att komma ihåg och lära sig. När du börjar bekanta dig med ett populärt programmeringsspråk är många termer tydliga och bekanta för dig från de språk du har studerat.

Vi kommer ihåg information genom att associera den med annan information. Jag har till exempel inga associationer till ordet funktör så det blir inte lätt för mig att lära mig den här terminen.

Min strategi för att lära mig sådana ord kom till mig ganska nyligen, och jag började använda den varje gång jag såg en term som jag inte förstod. Den består i urvalet av synonymer för de ord som du inte förstår och är obekanta med. Efter en tid lärde jag mig att plocka upp dessa synonymer.

Till exempel, funktör.

Haskell visar mycket på din skärm. Till exempel är en lista en funktion. Det betyder att en mappningsfunktion som använder en annan funktion visas i listan. Sedan skapat ny lista med resultat.

Karta (+1) -- resulterar i

Så jag gav namnet till detta - mapa. Ordet "karta" är väldigt lätt att komma ihåg. List är en funktionär. Listan är en karta.

Mitt felkontrollsystem

När jag skrev Python var mitt felsökningsverktyg print statements.

I Haskell använde jag mig av systematiska verktyg.

Men! Du kan använda Debug.Trace . Detta tillvägagångssätt liknar det i python funktion utskrift är oberoende av Haskell IO. OCH denna modul kan vara fördelaktigt i början. När du först började arbeta med Haskell, tänkte du på hur mycket du skulle använda det?

Om du inte använder trace-satser i din kod efter felkontroll kommer du genast att märka att din Haskell-kod ser sämre ut än i Python.

Den bästa handledningen om monader

Varje gång du hör om framgången för en Haskell-programmerare undrar du, "Hur behärskade den här personen monader?" Så allt är i sin ordning.

Jag måste analysera. Jag visste något om detta när jag skrev i Python. Men på grund av min oerfarenhet på detta område är det ganska svårt att göra en analys nu.

Okej, nu ska jag berätta mer. Jag kommer att förklara i Haskell.

Jag hittade en YouTube-video, "Parsing Stuff in Haskell", som visade hur man gör JSON-parsing i Haskell med hjälp av Parsec-biblioteket.

Den här videon hjälpte mig oavsiktligt att förstå hur du kan skapa det jag behövde med hjälp av monader och applikativa funktioner. Jag kom på hur deras funktioner (har, har) ansluter till varandra.

Efter att ha skrivit analysera Med hjälp av videon började jag förstå all kod. Jag började också förstå hela dess "natur". Men i det inledande skedet är detta inte användbart.

Så, Parsec gör sitt jobb ganska bra, så att min oerfarenhet av att skriva analys nästan inte märks. Precis som alla andra nybörjare i Haskell kunde jag enkelt göra bra program och operationer även i början av min bekantskap med detta språk.

Dra nytta av din kunskap

Haskell är mitt huvudspråk av flera anledningar:

  1. Valet av tekniker som jag kommer att använda.
  2. Jag kan skriva mina program snabbare, och för det mesta säljer jag dessa program.
  3. Du behöver inte ta itu med små buggar.
  4. Även när jag ställs inför några få fel löser jag dem snabbt, eftersom de är mer eller mindre tydliga.
  5. Python fokuserade inte på hastighet. Haskell gör detsamma, men valet är fortfarande upp till mig.
  6. Refaktorering är faktiskt ganska "blåsigt". I Python skällde jag ibland mycket på mig själv när jag glömde fixa små buggar i koden, vilket senare orsakade enorma pinsamheter.
  7. Fantastiska bibliotek. Huvuddragen hos Haskell är hög kvalitet bibliotek.
  8. Gemenskapen alltid redo att hjälpa.
  9. Enkel skalning av kod till önskad kärna.
  10. Haskell uppdateras ofta. Förra året, när GHC (kompilatorn för Haskell) uppgraderades till version 7.8, vilket gjorde det dubbelt så enkelt att skriva kod, fick många webbservrar snabbare fart.

Avslutningsvis vill jag säga att Haskell gav mig mycket glädje. Detta var bästa upplevelsen för hela mitt liv.

Jag har blivit tillfrågad dem många gånger. Jag svarar.

"Vad är din Haskell?"

Haskell är ett rent funktionellt programmeringsspråk. generell mening, kan användas för att lösa ett brett spektrum av problem. Sammanställt, men kan också bete sig som ett manus. Cross-plattform. Lat, med stark statisk skrivning. Och det är inte som andra språk. Alls.

"Vad är det här, något nytt språk?"

Inte alls. Haskells historia började 1987. Detta språk föddes i matematiska kretsar när en grupp människor bestämde sig för att skapa det bästa funktionella programmeringsspråket. 1990 släpptes den första versionen av språket, uppkallad efter den berömda amerikanske matematikern Haskell Curry. 1998 standardiserades språket och sedan 2000-talet har dess långsamma intåg i världen börjat. praktisk programmering. Under åren har språket förbättrats och 2010 såg världen sin uppdaterade standard. Vi har alltså att göra med ett språk som är äldre än Java.

"Och vem gjorde det?"

Haskell skapades av många människor. Den mest kända implementeringen av språket hittade sin förkroppsligande i GHC-kompilatorn (The Glasgow Haskell Compiler), född 1989 vid University of Glasgow. Kompilatorn har haft flera huvudutvecklare, varav två är mest kända, Simon Peyton Jones och Simon Marlow. Därefter gjorde ytterligare några hundra personer ett betydande bidrag till utvecklingen av GHC. Källa GHC-kompilatorn är öppen. Förresten, själva kompilatorn är till 82% skriven i Haskell.

För den nyfikna: För en definitiv redogörelse för historien om Haskell och GHC, läs här.

"Finns det bibliotek för Haskell?"

Åh ja! Det finns inte ens hundratals av dem - det finns tusentals av dem. Under läsningens gång kommer du att bekanta dig med många av dem.

"Och vad, är det redan möjligt i produktion?"

Den är redan i produktion. Sedan den första standarden släpptes har språket förbättrats, dess ekosystem har utvecklats, nya bibliotek har dykt upp och böcker har publicerats. Haskell är redo för allvar kommersiell användning, vilket framgår av berättelserna om framgångsrik implementering av Haskell i företag, inklusive stora.

"Är hindret för inträde i Haskell hög?"

Ja och nej. Att bemästra Haskell är svårt främst på grund av dess olikhet med andra språk, så människor som har erfarenhet av andra språk måste bryta hjärnan. Det är att bryta, och inte bara flytta dem: Haskell får dig att se även på bekanta saker på ett annat sätt. Å andra sidan är Haskell enklare än många kända språk. Ta inte mitt ord för det, du kommer snart att se själv. Och vet att många människor, efter att ha lärt sig smaken av Haskell, kategoriskt inte vill återvända till andra språk. Jag varnade dig.

"Och jag hörde också om några monader..."

Ja, det finns en sådan sak. Vissa saker från Haskells värld har inga direkta analoger i andra programmeringsspråk, och detta introducerar nybörjare i en dvala. Men oroa dig inte: jag har själv gått igenom denna stupor och förstår dig väl. Kom ihåg: det nya verkar bara skrämmande.

"Och om du jämför det med C++/Python/Scala..."

Att jämföra Haskell med andra språk ligger utanför den här bokens räckvidd. Det finns några gånger du kommer att se kodavsnitt på andra språk här, men jag inkluderar dem enbart för att betona skillnaden med Haskell, och inte alls för bättre/sämre jämförelser. I allmänhet kommer jag att göra mitt bästa för att inte berömma Haskell övermått, jag vill bara berätta sanningen om det. Jag har redan dragit min slutsats om detta språk, och du måste dra din egen slutsats om det.

    Typer

    Haskell-program är uttryck som utvärderas till värden. Varje värde har en typ. Intuitivt kan en typ helt enkelt förstås som en uppsättning tillåtna värden uttryck. För att ta reda på typen av något uttryck kan du använda tolkkommandot :type (eller :t). Du kan också köra kommandot: set +t för att få tolken att automatiskt skriva ut typen av varje beräknat resultat.
    Huvudtyperna av Haskell-språket är:
    Typerna Integer och Int används för att representera heltal och värdena typ heltal inte begränsad i längd.
    Flyttyper och Dubbel används för att representera reella tal.
    Bool-typen innehåller två värden: True och False, och är utformad för att representera resultatet av logiska uttryck.
    Teckentypen används för att representera tecken. Skriv namn i Haskell börjar alltid med stor bokstav.
    Haskell är ett starkt skrivet programmeringsspråk. Men i de flesta fall behöver programmeraren inte deklarera vilka typer de variabler han introducerar tillhör. Tolken själv kan härleda de typer av variabler som används av användaren.
    Men om det av någon anledning är nödvändigt att deklarera att ett visst värde tillhör en viss typ, används en konstruktion av formen: variabel:: Typ. Om +t-tolkningsalternativet är aktiverat, skriver det ut värden i samma format.
    Nedan är ett exempel på ett sessionsprotokoll för att arbeta med en tolk. Texten efter Prelude>-prompten förväntas skrivas in av användaren, och texten efter detta representerar systemets svar.

    Preludium>:set +t
    Preludium>1
    1:: Heltal
    Preludium>1.2
    1.2::Dubbel
    Prelude>'a'
    'a' :: Char
    Preludium>Sant
    Sant::Bool

    Från detta protokoll vi kan dra slutsatsen att värden av typen Integer, Double och Char är inställda enligt samma regler som i C-språket.
    Ett omfattande typsystem och stark typning gör Haskell-program typsäkra. Det är garanterat att i rätt program i Haskell används alla typer på rätt sätt. Rent praktiskt betyder detta att ett Haskell-program inte kan orsaka en åtkomstöverträdelse när det körs. Det är också garanterat att programmet inte kan använda oinitierade variabler. Således spåras många fel i programmet vid kompileringsstadiet och inte vid exekveringsstadiet.

    Aritmetisk

    Hugs-tolken kan användas för att utvärdera aritmetiska uttryck. I det här fallet kan du använda operatorerna +, -, *, / (addition, subtraktion, multiplikation och division) med de vanliga prioriteringsreglerna.
    Du kan också använda operatorn ^ (exponentiering). Så sessionen kan se ut så här:

    Preludium>2*2
    4:: Heltal
    Preludium>4*5 + 1
    21::Heltal
    Preludium>2^3
    8:: Heltal
    Dessutom kan du använda standarden matematiska funktioner sqrt( Roten ur), sin, cos, exp, etc. Till skillnad från många andra programmeringsspråk kräver Haskell inte parenteser när en funktion anropas. Så du kan bara skriva sqrt 2 istället för sqrt(2). Exempel:

    Preludium>sqrt 2
    1.4142135623731::Dubbel
    Preludium>1 + sqrt 2
    2.4142135623731::Dubbel
    Preludium>sqrt 2 + 1
    2.4142135623731::Dubbel
    prelude>sqrt (2 + 1)
    1.73205080756888::Dubbel

    Från detta exempel man kan dra slutsatsen att funktionsanropet har högre prioritet än aritmetiska operationer, så uttrycket sqrt 2 + 1 tolkas som (sqrt 2) + 1, inte sqrt (2 + 1). Parenteser bör användas för att specificera den exakta ordningen för utvärdering, som i det sista exemplet. (Egentligen har ett funktionsanrop högre prioritet än någon binär operator.)
    Det bör också noteras att till skillnad från de flesta andra programmeringsspråk, utvärderas heltalsuttryck i Haskell med ett obegränsat antal siffror (försök att utvärdera uttrycket 2^5000.) Till skillnad från C, där det maximala möjliga värdet för typen int begränsas av maskinens kapacitet (på modern personliga datorer det är 231-1 = 2147483647), kan Haskells heltalstyp lagra heltal av godtycklig längd.

    Tuples
    Förutom de enkla typerna som anges ovan kan Haskell definiera värden sammansatta typer. Till exempel, för att ange en punkt på ett plan, krävs två siffror som motsvarar dess koordinater. I Haskell kan ett par specificeras genom att lista komponenterna separerade med kommatecken och ta dem inom parentes: (5,3). Komponenterna i ett par behöver inte vara av samma typ: du kan göra ett par vars första element är en sträng, det andra elementet är ett nummer, och så vidare.
    I allmänhet, om a och b är några godtyckliga typer I Haskell betecknas typen av ett par där det första elementet är av typ a och det andra är av typ b (a,b). Till exempel är paret (5,3) av typen (heltal, heltal); paret (1, 'a') är av typen (heltal, Char). Du kan ta med mer komplext exempel: pair((1,'a'),1.2) är av typen ((Integer,Char),Double). Kolla upp det med en tolk. Observera att även om konstruktionerna av formen (1,2) och (Heltal,Heltal) ser likadana ut, refererar de till helt olika entiteter i Haskell. Det förra är värdet medan det senare är typen. Att arbeta med par i Haskell, det finns standardfunktioner fst och snd, som returnerar de första och andra elementen i paret (namnen på dessa funktioner kommer från de engelska orden "first" (first) och "second" (second)). Så de kan användas så här

    Prelude>fst (5, Sant)
    5:: Heltal
    Prelude>snd (5, Sant)
    Sant::Bool
    Förutom par kan trippel, fyrdubbling etc. definieras på liknande sätt. Deras typer är skrivna på samma sätt.
    Prelude>(1,2,3)
    (1,2,3) :: (Heltal,Heltal,Heltal)
    Prelude>(1,2,3,4)
    (1,2,3,4) :: (Heltal,Heltal,Heltal,Heltal)
    En sådan datastruktur kallas en tupel. En tuppel kan lagra en fast mängd heterogen data. Funktionerna fst och snd är endast definierade för par och fungerar inte för andra tupler. När du försöker använda dem, till exempel för trippel, ger tolken ett felmeddelande. Ett element i en tuppel kan vara ett värde av vilken typ som helst, inklusive en annan tupel. För att komma åt element av tupler som består av par, kan en kombination av funktionerna fst och snd användas. Följande exempel visar att man extraherar elementet 'a' från en tupel
    (1, ('a', 23.12)):
    Prelude>fst (snd (1, ('a', 23.12)))
    'a' :: heltal

    Listor
    Till skillnad från tuplar kan en lista lagra ett godtyckligt antal element. För att definiera en lista i Haskell måste du hakparentes lista dess element separerade med kommatecken. Alla dessa element måste vara av samma typ. Listtyp med objekt, tillhör typen a, betecknad som [a].

    Förspel>
    ::
    Prelude>['1','2','3']
    [’1’,’2’,’3’] ::
    Listan får inte innehålla något element. En tom lista betecknas som .
    Operatorn : (kolon) används för att lägga till ett element i början av en lista. Dess vänstra argument måste vara ett element, och dess högra argument måste vara en lista:
    Preludium>1:
    ::
    Prelude>'5':['1','2','3','4','5']
    [’5’,’1’,’2’,’3’,’4’,’5’] ::
    Preludium>False:
    ::
    Med operatorn (:) och en tom lista kan du bygga vilken lista som helst:
    Prelude>1:(2:(3:))
    :: heltal
    Operatorn (:) är högerassociativ, så du kan utelämna parenteserna i uttrycket ovan:
    Prelude>1:2:3:
    :: heltal
    Listelement kan vara alla värden - siffror, tecken, tupler, andra listor, etc.
    Prelude>[(1,'a'),(2,'b')]
    [(1,'a'),(2,'b')] :: [(Heltal,Char)]
    Preludium>[,]
    [,] :: []
    Att arbeta med listor i Haskell, det finns Ett stort antal funktioner. I denna laboratoriearbete Låt oss bara titta på några av dem.
    Head-funktionen returnerar det första elementet i listan.
    Svansfunktionen returnerar en lista utan det första elementet.
    Längdfunktionen returnerar längden på en lista.
    Huvud- och svansfunktionerna är definierade för icke-tomma listor. När du försöker tillämpa dem på tom lista tolken rapporterar ett fel. Exempel på att arbeta med de angivna funktionerna:
    Preludium>huvud
    1:: Heltal
    Preludium>svans
    ::
    Preludium>svans
    :: heltal
    Preludium>längd
    3::Int
    Observera att resultatet av längdfunktionen är typ Int, inte av typen heltal.
    För att sammanfoga (sammanfoga) listor definierar Haskell operatorn ++.
    Preludium>++
    :: heltal

    Strängar
    Strängvärden i Haskell, som i C, anges i dubbla citattecken. De är av typen String.
    Preludium>"hej" "hej" :: String
    Strängar är egentligen listor över tecken; alltså uttrycken "hej", ['h','e','l','l','o'] och

    'h':'e':'l':'l':'o': betyder samma sak, och String-typen är en synonym för . Alla funktioner för att arbeta med listor kan användas när man arbetar med strängar:
    Preludium>huvud "hej"
    'h' :: Char
    Prelude>svans "hej"
    "Hallå" ::
    Preludium>längd "hej"
    5::Int
    Preludium>"hej" ++ ", värld"
    "Hej världen" ::
    För konvertering numeriska värden till strängar och vice versa, det finns läs- och visa-funktioner:
    Förspel>föreställning 1
    "1" ::
    Prelude>"Formel" ++ show 1
    "Formel 1" ::
    Prelude>1 + läs "12"
    13::Heltal
    Om show-funktionen inte lyckas konvertera strängen till ett tal kommer den att rapportera ett fel.

    Funktioner
    Hittills har vi använt Haskells inbyggda funktioner. Nu är det dags att lära sig identifiera egna funktioner. För att göra detta måste vi lära oss några fler tolkkommandon (kom ihåg att dessa kommandon kan förkortas till en bokstav):
    Kommandot :load låter dig ladda Haskell-programmet som finns i den angivna filen till tolken.
    Kommandot :edit startar processen med att redigera den senast laddade filen.
    Kommandot :reload läser om den senast laddade filen. Definitioner anpassade funktioner måste finnas i en fil som ska laddas in i Hugs-tolken med kommandot :load.
    Du kan använda kommandot :edit för att redigera det nedladdade programmet. Den startar en extern redigerare (Anteckningar som standard) för att redigera filen. Efter att redigeringssessionen är slut måste redigeraren stängas; detta kommer att få Hugs-tolken att läsa om innehållet i den ändrade filen. Men filen kan också redigeras direkt från Windows-skalet. I det här fallet, för att tolken ska kunna läsa filen igen, måste du uttryckligen anropa kommandot :reload.
    Tänk på ett exempel. Skapa en lab1.hs-fil i någon katalog. Låt vara fullständig sökväg till den här filen - c:\labs\lab1.hs (detta är bara ett exempel, dina filer kan ha ett annat namn). Kör följande kommandon i Hugs-tolken:

    Prelude>:ladda "c:\\labs\\lab1.hs"
    Om nedladdningen lyckas ändras tolkprompten till Main>. Faktum är att om modulnamnet inte anges anses det vara lika med Main.
    Huvud>:redigera
    Ett redigeringsfönster bör öppnas här, där du kan skriva in programmets text. Stiga på:
    x=
    Spara filen och stäng redigeraren. Hugs-tolken laddar filen
    c:\labs\lab1.hs och nu kommer värdet av x att bestämmas:
    Main>x
    ::
    Observera att när du skriver filnamnet i kommandot argument:load, dupliceras \-tecken. Precis som i C-språket fungerar tecknet \ i Haskell som en indikator på början av ett tjänstecken ('\n', etc.) För att skriva in tecknet \ direkt, är det nödvändigt, som i C, för att undvika det med ett annat tecken \ .
    Nu kan vi gå vidare till att definiera funktioner. Skapa, i enlighet med processen som beskrivs ovan, en fil och skriv in följande text i den:

    kvadrat::Heltal -> Heltal
    kvadrat x = x * x

    Den första raden (square::Integer -> Integer) förklarar att vi definierar en funktionskvadrat som tar en Integer-parameter och returnerar ett heltalsresultat. Den andra raden (kvadrat x = x * x) är själva funktionsdefinitionen. Kvadratfunktionen tar ett argument och returnerar dess kvadrat. Funktioner i Haskell är "förstklassiga" värden. Det betyder att de är "lika" med värden som heltal och riktiga nummer, tecken, strängar, listor osv. Funktioner kan skickas som argument till andra funktioner, returneras från funktioner och så vidare. Som alla värden i Haskell har funktioner en typ. Typen av en funktion som tar värden av typ a och returnerar värden av typ b betecknas som a->b.
    Ladda den genererade filen i tolken och kör följande kommandon:

    Huvud>:typ kvadrat
    kvadrat::Heltal -> Heltal
    huvud> ruta 2
    4:: Heltal
    Observera att deklarationen av typen av funktionskvadrat i princip inte var nödvändig: tolken själv kunde sluta sig till nödvändig information om funktionstypen från dess definition. Men för det första skulle den härledda typen vara mer generell än heltal -> heltal, och för det andra är att uttryckligen specificera typen av en funktion "god form" vid programmering i Haskell, eftersom typdeklarationen fungerar som en sorts dokumentation för funktionen och hjälper till att upptäcka programmeringsfel.
    Namn på användardefinierade funktioner och variabler måste börja med en liten latinsk bokstav. Resten av tecknen i namnet kan vara versaler eller gemener. med latinska bokstäver, siffror eller symbolerna _ och ’ (understreck och apostrof). Följande är alltså exempel på giltiga variabelnamn:

    var
    var1
    variabelnamn
    variabelnamn
    var'

    Villkorliga uttryck

    Du kan använda villkorliga uttryck i en Haskell-funktionsdefinition. Låt oss skriva en funktionssignum som beräknar tecknet på argumentet som skickas till det:

    signum:: Heltal -> Heltal
    signum x = om x > 0 så 1
    annat om x annat 0

    Det villkorliga uttrycket skrivs som:
    om tillstånd sedan uttryck annat uttryck. Observera att även om detta uttryck ser ut som motsvarande operator i C eller Pascal, måste både den dåvarande delen och den andra delen vara närvarande i ett Haskell-villkorsuttryck. Uttryck i då-delen och i else-delen villkorlig operatör måste vara av samma typ. Villkoret i definitionen av ett villkorligt uttalande är vilket uttryck som helst av typen Bool. Jämförelser är exempel på sådana uttryck. När du jämför kan du använda följande operatorer:
    , = - dessa operatorer har samma betydelse som i C-språket (mindre än, större än, mindre än eller lika med, större än eller lika med).
    == - operatör för jämställdhetstest.
    /= - operator för kontroll av ojämlikhet.
    Bool-typuttryck kan kombineras med common logiska operatorer&& och || (AND och OR), och negationsfunktioner inte.
    Exempel på giltiga villkor:
    x >= 0 && x x > 3 && x /= 10
    (x > 10 || x Naturligtvis kan du definiera dina egna funktioner som returnerar Bool-värden och använda dem som villkor. Till exempel kan du definiera en isPositive-funktion som returnerar True om dess argument är icke-negativt och False annars :
    isPositive::Heltal -> Bool
    isPositive x = om x > 0 då Sant annars Falskt

    Signumfunktionen kan nu definieras enligt följande:
    signum:: Heltal -> Heltal
    signum x = om är Positivt x så 1
    annat om x annat 0
    Observera att isPositive-funktionen kan definieras enklare:
    är positiv x = x > 0

    Informationen hämtades från: http://sguprog.narod.ru/