Differences
This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
pp:l04x [2020/03/12 13:06] pdmatei created |
pp:l04x [2021/04/05 14:51] (current) alexandru.poboranu add a Context in the last 2 ex |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Tipuri în Haksell ====== | + | ====== 5. Algebraic Datatypes in Haskell ====== |
- | Scopul laboratorului: | + | There are multiple ways of defining new data types in Haskell. In this session, we will focus on //Algebraic Data Types//, which allow us to specify multiply constructors, with any number of arguments. |
- | * introducerea conceptului de Tipuri de Date Algebrice | + | A new ADT can be defined using the keyword ''data'' followed by the desired name (necessarily capitalized!), an equal sign and then all constructors (capitalized!) separated by a pipe ''|'': |
- | * introducerea conceptului de Tipuri de Date Abstracte | + | |
- | * clarificarea distincției dintre acestea | + | |
- | * introducerea conceptului de tipuri de date monomorfice/polimorfice | + | |
- | * definirea și lucrul cu propriile tipuri de date în Haskell | + | |
- | + | ||
- | ===== Tipuri de Date Algebrice ===== | + | |
- | + | ||
- | [[https://wiki.haskell.org/Algebraic_data_type|Tipurile de date algebrice]] sunt tipurile pentru care putem specifica //forma// elementelor. Acestea pot avea //unul sau mai mulți// constructori, iar constructorii pot avea //zero sau mai multe// câmpuri. | + | |
- | + | ||
- | În Haskell, definirea unui tip se face folosind cuvântul cheie ''data'' urmat de numele tipului, semnul egal, apoi constructorii: | + | |
<code haskell> | <code haskell> | ||
- | -- Tipul de date "Color" are doi constructori posibili; bara "|" indică | + | data Ordering = LT | EQ | GT |
- | -- faptul că o culoare poate fi ori "White", ori "Black" (similar cu tipul "Bool"). | + | |
- | data Color = White | Black | + | |
- | + | ||
- | -- Constructorul "Point" este practic doar un tuplu cu nume; un (Float, Float) mai specific. | + | |
- | data Point = Point Float Float | + | |
</code> | </code> | ||
- | <note> | + | This type is already defined in haskell and is used whenever comparisons are needed (its constructors stand for "less than", "equal" and "greater than"). Think of how the standard C function ''strcmp'' takes two strings and returns a value indicating their lexicographic order: |
- | Observați că, în cel de-al doilea caz, tipul de date și constructorul au același nume, "Point". Acest lucru e perfect ok, deoarece reprezintă concepte diferite. | + | |
- | </note> | + | |
- | Desigur, putem combina aceste două concepte (multiplii constructori, multiple câmpuri ale unui singur constructor): | + | * a negative number if the first string comes first |
+ | * 0 if they are equal | ||
+ | * a positive number if the second string comes first | ||
- | <code haskell> | + | In Haskell, it would return an ''Ordering''. |
- | -- O formă este fie un cerc definit de centru și rază, fie un | + | |
- | -- dreptunghi definit de două colțuri opuse. | + | |
- | data Shape = Circle Point Float | Rectangle Point Point | + | |
- | </code> | + | |
- | <note warning> | + | 1. Write a function ''myCompare'' which takes two integers and returns an ''Ordering'' indicating their relationship (there is already a function in Haskell, ''compare'', which does this for all types of numbers). |
- | Numele tipurilor de date și numele constructorilor trebuie scrise cu literă mare. | + | |
- | </note> | + | |
- | ==== Pattern matching ==== | + | 2. Write a function which takes an integer and a **sorted** (ascending) list of integers and inserts that number into the proper position so that the resulting list is still sorted. |
- | Aduceți-vă aminte de mecanismul de "pattern matching" din Haskell (pe care până acum l-ați folosit predominant pentru liste). Acestea functionează pe //constructori//, deci îl putem folosi pentru tipurile definite de noi: | + | <code> |
- | + | insert 7 [1,5,22,86] -> [1,5,7,22,86] | |
- | * putem avea definiții diferite pentru constructori diferiți | + | |
- | * putem asocia valori constituente cu nume, pe care să le folosim în expresia funcției | + | |
- | + | ||
- | <code haskell> | + | |
- | colorToString :: Color -> String | + | |
- | colorToString White = "White" | + | |
- | colorToString Black = "Black" | + | |
- | + | ||
- | pointsDistance :: Point -> Point -> Float | + | |
- | pointsDistance (Point x1 y1) (Point x2 y2) = sqrt ((x1 - x2) ** 2 + (y1 - y2) ** 2) | + | |
- | + | ||
- | area :: Shape -> Float | + | |
- | area (Circle _ r) = pi * (r ** 2) | + | |
- | area (Rectangle (Point x1 y1) (Point x2 y2)) = (y2 - y1) * (x2 - x1) | + | |
</code> | </code> | ||
- | ===== Tipuri de Date Abstracte ===== | + | 3. Write a function ''insertSort'' that makes use of ''insert'' to implement [[https://en.wikipedia.org/wiki/Insertion_sort|insertion sort]] on a list. |
- | [[https://wiki.haskell.org/Abstract_data_type|Tipurile de date abstracte]] sunt tipurile care au asociată o //interfață//, dar nu și o //implementare//. De exemplu, o listă poate fi văzută ca o structură care suportă anumite operații: ''isEmpty'', ''length'', ''head'' etc. Utilizatorul listei poate fi preocupat doar de comportamentul acestei interfețe (i.e. colecția de funcții), nu și de detalii de implementare. De exemplu, o listă poate fi reprezentată ca o zonă contiguă de memorie ([[https://en.wikipedia.org/wiki/Dynamic_array|Array List]]), sau ca o colecție de celule care conțin o valoare și adresa următoarei celule ([[https://en.wikipedia.org/wiki/Linked_list|Linked List]]). Unul din avantajele acestei separări - între comportament și implementare - este că dezvoltatorul unei biblioteci poate modifica implementarea unei structuri, lăsând interfața neschimbată; ceea ce nu necesită modificări în codul client. | + | We now have a sorting function. However, it is unsatisfactory, because it can only sort numbers in an ascending order. We cannot specify any other criteria (e.g. descending order, by number of prime factors, by number of digits). We will expand the functionality by employing the ''Ordering'' data type and an auxiliary "comparator function" which takes two integers and returns an ''Ordering''. |
- | Conceptele de "tip de date abstract" și "tip de date algebric" sunt ortogonale, ele vizând trăsături diferite. | + | 4. Write a function ''insertBy'' which takes a comparator function, an integer and a sorted list of integers (s.t. $ \forall i, j; i < j, comparator(l_i, l_j) \neq GT$) and uses the comparator to insert the element at the proper location. |
- | ===== Tipuri polimorfice ===== | + | 5. Write a function ''insertSortBy'' which insert-sorts a list, with the aid of a comparator function (there is a function in the ''Data.List'' module named ''sortBy'' that does the same thing, though it uses another sorting algorithm). |
- | Vrem să definim propria noastră listă care poate conține numere întregi: | + | A data type's constructors can also take arguments; when defining the data type, we need to specify the types of these arguments: |
<code haskell> | <code haskell> | ||
- | data List = Empty | Cons Int List | + | data Point = Point Float Float |
</code> | </code> | ||
- | Acesta este un tip de date //monomorfic//, deoarece operează cu un singur tip, anume ''Int''. Foarte multe operații pe listă nu sunt preocupate de tipul sau de valorile elementelor: verificarea dacă lista este goală, calculul lungimii listei, inversarea listei etc. Redefinirea listei, precum și a tuturor acestor operații pentru fiecare tip pentru care am avea nevoie de o listă, ar necesita multă muncă. | + | Note that the constructor here can have the same name as the data type, because they are very different things and could never cause confusion (they can never appear in the same contexts). |
- | Soluția este să definim un tip de date //polimorfic//, parametrizat cu tipul elementelor pe care le conține: | + | Once defined this way, we can use regular **pattern matching** for our constructors: |
<code haskell> | <code haskell> | ||
- | data List a = Empty | Cons a (List a) | + | pointToPair :: Point -> (Float, Float) |
+ | pointToPair (Point x y) = (x, y) | ||
</code> | </code> | ||
- | Simbolul ''a'' reprezintă o //variabilă de tip//. O variabilă de tip trebuie reprezentată de un nume care începe cu literă mică. Prezența variabilelor de tip reprezintă o formă de //abstracție//; ele pot fi înlocuite de un tip propriu-zis pentru a obține un tip concret, e.g. ''List Int'', ''List Char'', ''List (List Char)'' etc. | + | 6. Write a function ''distance'' which takes two points and computes their euclidian distance. |
- | Putem astfel să scriem funcții polimorfice care operează pe lista noastră și care nu sunt preocupate de tipul conținutului: | + | 7. Write a function ''collinear'' which takes three points and tells whether they are on the same line. (one way to do this is to compute the area of the triangle they define and make sure it's 0). |
+ | |||
+ | <note tip> | ||
+ | If you ask ''ghci'' to print a ''Point'' (e.g. by simply typing ''Point 2.0 3.1'' at the prompt) it will throw an error, as it doesn't know how to do that. You can use the following definition to obtain a "default" representation. | ||
<code haskell> | <code haskell> | ||
- | myLength :: List a -> Int | + | data Point = Point Float Float deriving Show |
- | myLength Empty = 0 | + | |
- | myLength (Cons _ t) = 1 + myLength t | + | |
- | + | ||
- | myHead :: List a -> a | + | |
- | myHead (Cons a _) = a | + | |
</code> | </code> | ||
- | Observați că, înlocuind ''List a'' cu ''[a]'', semnăturile acestor funcții se potrivesc cu cele ale ''length'' și ''head'' din ''Prelude''. | + | The meaning of ''deriving Show'' will be discussed in a future session. |
- | + | ||
- | <note> | + | |
- | Ce este ''List''? | + | |
- | + | ||
- | În definirea unei liste polimorfice, am scris un nou tip generic ''List a''. Dar ce anume reprezintă pentru Haskell ''List''? ''List'' este un **constructor de tip**, nu un tip de date. Constructorii de tip primesc ca parametrii //tipuri// și întorc un tip (sau un alt constructor de tip dacă nu primesc suficienți parametrii). | + | |
- | + | ||
- | Asta înseamnă că ''List'' nu este un //tip//, dar ''List Int'', ''List Char'' etc. sunt tipuri. | + | |
</note> | </note> | ||
- | ==== Constante polimorfice ==== | + | We can also define **recursive data types**, whose constructors can depend on another value of the same type. |
- | Care este tipul listei vide? | + | <code haskell> |
- | + | data Natural = Zero | Succ Natural | |
- | <code> | + | |
- | Prelude> :t [] | + | |
- | [] :: [a] | + | |
</code> | </code> | ||
- | Observăm că tipul listei vide este general. Asta înseamnă că lista vidă poate fi tratată ca o listă de orice tip. | + | Here, we say that a natural number is either the number zero, or the successor of another natural number (see [[https://en.wikipedia.org/wiki/Peano_axioms|Peano axioms]]). The number three would be ''Succ (Succ (Succ Zero)))''. |
- | <code> | + | 8. Write a function ''add'' which takes two ''Natural'' numbers and returns their sum (remember that you can do pattern matching to check the structure of a number). |
- | Prelude> [1,2,3] ++ "123" -- eroare, tipuri diferite | + | |
- | Prelude> [1,2,3] ++ [] | + | |
- | [1,2,3] | + | |
- | Prelude> "123" ++ [] | + | |
- | "123" | + | |
- | </code> | + | |
- | Spunem că ''[]'' este o **constantă polimorfică**. | + | 9. Write a function ''mul'' which takes two ''Natural'' numbers and returns their product. |
- | <note> | + | <note tip> |
- | O altă constantă polimorfică este, de exemplu ''1''. De aceea putem scrie: | + | Again, it might be useful to display these data types so add ''deriving Show'' to the definition: |
- | <code> | + | <code haskell> |
- | Prelude> 1 + 2 | + | data Natural = Zero | Succ Natural deriving Show |
- | 3 | + | |
- | Prelude> 1 + 2.71 | + | |
- | 3.71 | + | |
</code> | </code> | ||
- | |||
- | Dacă forțăm 1 să fie întreg, vom primi o eroare la a doua adunare: | ||
- | <code> | ||
- | Prelude> (1 :: Int) + 2.71 | ||
- | |||
- | <interactive>:60:14: | ||
- | No instance for (Fractional Int) arising from the literal '2.71' | ||
- | In the second argument of '(+)', namely '2.71' | ||
- | In the expression: (1 :: Int) + 2.71 | ||
- | In an equation for 'it': it = (1 :: Int) + 2.71 | ||
- | </code> | ||
- | |||
- | ''1'' are însă o constrângere despre care vom discuta în laboratorul următor. | ||
</note> | </note> | ||
- | ===== Exemple ===== | + | Using recursive data types, we can also define immutable lists of integers: |
- | + | ||
- | ==== Maybe ==== | + | |
- | ''Maybe'' este un tip //polimorfic// care modelează prezența unei anumite valori, sau absența acesteia: | ||
<code haskell> | <code haskell> | ||
- | data Maybe a = Nothing | Just a | + | data IList = IVoid | ICons Int IList |
</code> | </code> | ||
- | Probabil ați scris și folosit funcții care ar trebui să se descurce cu anumite cazuri în care nicio valoare din codomeniu nu se potrivește ca rezultat. Să considerăm, de exemplu, o funcție care calculează suma primului și al ultimului element dintr-o listă de numere întregi. Dacă tipul acesteia ar fi: | + | However, we have seen that haskell lists are generic, capable of containing any type of element, not just integers. For this we need a **polymorphic data type** (as opposed to all the previous ones which are **monomorphic**). That is, a type that depends on another type. |
<code haskell> | <code haskell> | ||
- | sumExtremities :: [Int] -> Int | + | data List a = Void | Cons a (List a) |
</code> | </code> | ||
- | ce am putea întoarce pentru lista vidă? O idee ar fi să întoarcem o valoare default (e.g. ''0''); sau o valoare ca ''-1'' și să impunem ca lista primită să conțină doar numere pozitive. O soluție mai elegantă este să îmbogățim domeniul valorilor returnate cu o valoare specială, care semnalează că nu putem calcula această sumă: | + | In the above definition, we have mostly replaced ''Int'' with ''a''. ''a'' is a **type variable**; it can stand for any type (what makes it a type variable is that it starts with a lowercase letter; it could as well be ''b'' or ''something''). We can now construct lists of any type. However, remember that all elements in a list must be of the //same// type; we can't combine integers and strings. That is why ''a'' also follows the data type name, on the left hand side of the ''='': if we have a ''List'' which contains ''Int''s, then that is a ''List Int''. |
- | <code haskell> | + | 10. Write a function ''myLength'' which takes a ''List a'' and returns its length. Notice how the type of the lists elements doesn't matter (and remember pattern matching!). |
- | sumExtremities :: [Int] -> Maybe Int | + | |
- | sumExtremities [] = Nothing | + | |
- | sumExtremities [_] = Nothing | + | |
- | sumExtremities l = Just (head l + last l) | + | |
- | </code> | + | |
- | <note> | + | 11. Write a function ''toHaskell'' which converts our lists to haskell's lists. That is, it takes a ''List a'' and returns a ''[a]''. |
- | Pentru o listă cu un singur element, ar putea avea sens să-l considerăm și primul și ultimul, deci să returnăm dublul lui. Abordarea noastră e doar o convenție. | + | |
- | </note> | + | |
- | Definim o funcție care întoarce un șir descriptiv al sumei calculate: | + | 12. Define a **recursive**, **polymorphic** data type to represent binary trees. A binary tree is either the empty tree, or a node containing a value, a left subtree and a right subtree. |
- | <code haskell> | + | 13. Write a function ''height'' which takes a binary tree and returns its height. |
- | describeSum :: [Int] -> String | + | |
- | describeSum l = case sumExtremities l of | + | |
- | Nothing -> "Can't calculate sum" | + | |
- | Just x -> "Sum is " ++ show x | + | |
- | </code> | + | |
- | Putem vedea cum funcționează, din ''ghci'': | + | 14. Write a function ''size'' which takes a binary tree and returns the number of nodes. |
- | + | ||
- | <code> | + | |
- | *Main> describeSum [] | + | |
- | "Can't calculate sum" | + | |
- | *Main> describeSum [8] | + | |
- | "Can't calculate sum" | + | |
- | *Main> describeSum [8, 12, 35] | + | |
- | "Sum is 43" | + | |
- | *Main> | + | |
- | </code> | + | |
- | Concepte similare se regăsesc și în alte limbaje de programare moderne: | + | 15. Write a function ''mirror'' which takes a tree and mirrors it (for each node, the left subtree becomes the right subtree and vice-versa). |
- | * C%%+%%%%+%%17 - [[https://en.cppreference.com/w/cpp/utility/optional|std::optional]] | + | 16. Write a function ''treeMap'' which takes a function ''a -> b'' and a tree with values of type ''a'' and applies the function on each of the tree's elements to yield a tree with values of type ''b'' (exactly like ''map'' for lists). |
- | * Java8 - [[https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html|Optional]] | + | |
- | * Swift - [[https://developer.apple.com/documentation/swift/optional|Optional]] | + | |
- | * Scala - [[https://www.scala-lang.org/api/current/scala/Option.html|Option]] | + | |
- | * Rust - [[https://doc.rust-lang.org/std/option/index.html|std::option]] | + | |
- | ==== Either ==== | + | 17. Write a function ''flatten'' which takes a binary tree and collects all its values into a list (note that there are multiple ways to go through all the values, such as [[https://en.wikipedia.org/wiki/Tree_traversal|preorder, inorder, postorder]]; the choice is up to you). |
- | Un TDA similar este ''Either'', care modelează valori cu două tipuri posibile: | + | Consider the following data type which defines a ''Student'', charactarized by a first name, a last name and a list of grades: |
<code haskell> | <code haskell> | ||
- | data Either a b = Left a | Right b | + | data Student = Student String String [Float] |
</code> | </code> | ||
- | Prin convenție, constructorul ''Left'' reprezintă o eroare, iar constructorul ''Right'' o valoare corectă. Astfel putem extinde mecanisme de error-handling pentru a conține mai multe informații despre ce a cauzat eroarea (spre deosebire de ''Maybe'', care doar indică **apariția** unei erori): | + | 18. Write a function ''avg'' to compute the average of a student's grades. |
- | Adaptăm cele două funcții de mai sus: | + | <note tip> |
- | + | If you use ''length'' on a list and want to divide to it, you'll notice haskell raises an error when asked to divide a ''Float'' to an ''Int''; you can call ''fromIntegral'' on an ''Int'' to perform an explicit conversion. | |
- | <code haskell> | + | </note> |
- | sumExtremities :: [Int] -> Either String Int | + | |
- | sumExtremities [] = Left "Can't sum on empty list" | + | |
- | sumExtremities [_] = Left "Can't sum on singleton list" | + | |
- | sumExtremities l = Right (head l + last l) | + | |
- | + | ||
- | describeSum :: [Int] -> String | + | |
- | describeSum l = case sumExtremities l of | + | |
- | Left e -> e | + | |
- | Right x -> "Sum is " ++ show x | + | |
- | + | ||
- | </code> | + | |
- | + | ||
- | <code> | + | |
- | *Main> describeSum [] | + | |
- | "Can't sum on empty list" | + | |
- | *Main> describeSum [8] | + | |
- | "Can't sum on singleton list" | + | |
- | *Main> describeSum [8, 12, 35] | + | |
- | "Sum is 43" | + | |
- | *Main> | + | |
- | </code> | + | |
- | Concepte similare se regăsesc și în alte limbaje de programare moderne: | + | 19. Write a function ''studComp'' which takes two students and returns an ''Ordering'' based on their averages. |
- | * C%%+%%%%+%%17 - [[https://en.cppreference.com/w/cpp/utility/variant|std::variant]] | + | 20. Write a function ''highestAverage'' which takes a list of students and returns the name and surname (separated by a space) of the student with the highest average. |
- | * Scala - [[https://www.scala-lang.org/api/2.9.3/scala/Either.html|Either]] | + | |
- | ===== Înregistrări ===== | ||
- | Vrem să definim un tip de date pentru a descrie un student, folosind următoarele câmpuri: nume, prenume, an de studiu, medie. | + | The following data type models simple arithmetic expressions, where we can have variables, integer constants and addition and multiplication: |
<code haskell> | <code haskell> | ||
- | -- tipul de date și constructorul pot avea același nume, | + | data AExpr = Const Int | Var String | Add AExpr AExpr | Mul AExpr AExpr |
- | -- pentru că reprezintă concepte diferite. | + | |
- | data Student = Student String String Int Float | + | |
</code> | </code> | ||
- | Observăm că semnficația câmpurilor nu este evidentă din definiție. | + | Similarly, the following models simple boolean expressions that are either an equality/greather-than comparison between two arithmetic expressions or a negation of a boolean expressions: |
- | + | ||
- | Dacă am vrea să putem extrage câmpurile unei valori de tip ''Student''? Definim funcțiile: | + | |
<code haskell> | <code haskell> | ||
- | nume :: Student -> String | + | data BExpr = Eq AExpr AExpr | Not BExpr | Gt AExpr AExpr |
- | nume (Student n _ _ _) = n | + | |
- | + | ||
- | prenume :: Student -> String | + | |
- | prenume (Student _ p _ _) = p | + | |
- | + | ||
- | an :: Student -> Int | + | |
- | an (Student _ _ a _) = a | + | |
- | + | ||
- | medie :: Student -> Float | + | |
- | medie (Student _ _ _ m) = m | + | |
</code> | </code> | ||
- | Astfel de cod (necesar, neinteresant, laborios) poartă denumirea de [[https://en.wikipedia.org/wiki/Boilerplate_code|"boilerplate"]]. Adesea, limbajele de nivel înalt oferă construcții sintactice pentru a evita scrierea "de mână". În Haskell, putem scrie: | + | Our goal is to be able to evaluate an expression thus represented. First, we need a way to deal with variables: for this we define a context, which is a collection of mappings from a variable name to a value: |
<code haskell> | <code haskell> | ||
- | data Student = Student { nume :: String | + | type Context = [(String, Int)] |
- | , prenume :: String | + | |
- | , an :: Int | + | |
- | , medie :: Float | + | |
- | } | + | |
</code> | </code> | ||
- | | ||
- | Avem astfel aceeași funcționalitate în mult mai puține linii. Există și alte avantaje ale acestei sintaxe, precum și [[https://wiki.haskell.org/Name_clashes_in_record_fields|capcane]]. Citiți mai multe [[https://en.wikibooks.org/wiki/Haskell/More_on_datatypes#Named_Fields_(Record_Syntax)|aici]]. | ||
- | |||
- | |||
- | ===== Exerciții ===== | ||
- | |||
- | ==== 1. Arbori binari ==== | ||
- | |||
- | Fie următorul tip care modelează arbori binari polimorfici: | ||
- | |||
- | <code haskell> | ||
- | data BTree a = Nil | Node a (BTree a) (Btree a) | ||
- | </code> | ||
- | |||
- | a. definiți o funcție ''foldrT'', echivalentul lui ''foldr'' pentru arbori\\ | ||
- | b. definiți o funcție ''mapT'' echivalentul lui ''map'' pentru arbori\\ | ||
- | c. definiți o funcție ''zipWithT'', echivalentul lui ''zipWith'' pentru arbori | ||
<note> | <note> | ||
- | Orientați-vă după tipurile funcțiilor prezente în schelet. | + | Note that we used the more lightweight ''type'' to obtain a **type synonym**, because we don't actually need the power of ADTs. |
</note> | </note> | ||
- | ==== 2. Tabele asociative ==== | + | 21. Write a function ''search'' which takes a ''Context'' and a variable name and returns the value from that context (it is guaranteed, that exactly one value exists for each variable on which the function is called; you don't need to account for any edge-case). |
- | + | ||
- | O [[https://en.wikipedia.org/wiki/Associative_array|tabelă asociativă]] este o colecție de perechi (cheie, valoare). O cheie prezentă în tabelă are asociată o anumită valoare și putem folosi tabela pentru a accesa valori pe baza unei chei. Vom reprezenta o tabelă asociativă ca o listă de perechi, folosind cuvântul cheie ''type'' pentru a defini un [[https://wiki.haskell.org/Keywords#type|alias]]: | + | |
- | + | ||
- | <code haskell> | + | |
- | type Assoc k v = [(k, v)] | + | |
- | </code> | + | |
- | + | ||
- | a. definiți o funcție care primește o cheie, o valoare, o tabelă asociativă și întoarce o nouă tabelă care conține noua asociere (fie adăugând-o, fie modificând-o)\\ | + | |
- | b. definiți o funcție care primește o cheie și o tabelă asociativă și întoarce valoarea asociată cheii, împachetată într-un ''Maybe''\\ | + | |
- | c. definiți o funcție care primește o cheie și o tabelă asociativă și întoarce tabela fără cheia dată\\ | + | |
- | d. definiți o funcție care primește o tabelă asociativă și întoarce o listă de chei | + | |
- | + | ||
- | ==== 3. Numere naturale extinse ==== | + | |
- | + | ||
- | Fie mulțimea de numere naturale extinse cu un punct la infinit ($ \hat{\mathbb{N}} = \mathbb{N} \cup \{ \infty \}$). | + | |
- | + | ||
- | <code haskell> | + | |
- | data Extended = Infinity | Value Integer | + | |
- | </code> | + | |
- | + | ||
- | a. definiți o funcție ''extSum'' care să evalueze suma a două numere naturale extinse\\ | + | |
- | b. definiți o funcție ''extDiv'' care să evalueze raportul a două numere naturale extinse (împărțirea la 0 și la infinit are rezultat definit)\\ | + | |
- | c. definiți o funcție ''extLess'' care spune dacă un număr natural extins e mai mic decât un altul | + | |
- | {{:pp:lab4_-_schelet.zip|Laborator 4 - Schelet}}\\ | + | 22. Write a function ''evalA'' which takes a ''Context'' and an ''AExpr'' and returns a single integer representing its evaluated value. |
- | + | ||
- | ===== Recommended Reading ===== | + | |
- | * [[http://learnyouahaskell.com/types-and-typeclasses|Learn you a Haskell for Great Good - Chapter 2: Types and Typeclasses]] | + | 23. Write a function ''evalB'' which takes a ''Context'' and a ''BExpr'' and returns a single boolean representing its evaluated value. |
- | * [[http://learnyouahaskell.com/making-our-own-types-and-typeclasses|Learn you a Haskell for Great Good - Chapter 8: Making Our Own Types and Typeclasses]] | + | |
- | * [[http://book.realworldhaskell.org/read/defining-types-streamlining-functions.html|Real World Haskell - Chapter 3: Defining Types, Streamlining Functions]] | + | |
- | * [[http://book.realworldhaskell.org/read/types-and-functions.html|Real World Haskell - Chapter 4: Types and Functions]] | + | |