589 lines
15 KiB
Plaintext
589 lines
15 KiB
Plaintext
|
Les fonctions et les procédures
|
|||
|
================================
|
|||
|
|
|||
|
Préliminaire : rappel de théorie de cybernétique
|
|||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|||
|
Théorie de l'information (Claude Shannon, 1949), (ou théorie de la communication)
|
|||
|
|
|||
|
Canal de transmission::
|
|||
|
|
|||
|
entrée -> récepteur -> émetteur -> sortie
|
|||
|
|
|||
|
.. glossary::
|
|||
|
|
|||
|
cybernétique
|
|||
|
|
|||
|
étude des fonctions de réflexes conditionnés du cerveau humain
|
|||
|
utilisation au mieux en PNL ("programmation neuro-linguistique")
|
|||
|
ou en analyse transactionnelle, ou au pire en ingérinerie sociale.
|
|||
|
|
|||
|
La matérialité physique est considérée comme le hardware, le génétique (le
|
|||
|
réseau neuronal) étant assimilé au network hardware.
|
|||
|
|
|||
|
Les objets mentaux (fonctionnements psychologiques et épigénétiques du
|
|||
|
cerveaux) est assimilé au logiciel, au software.
|
|||
|
|
|||
|
IFTTT ("if this then that") : la causalité mondaine est ramenée à un ordre de
|
|||
|
comportement affecté à un assimilé-machine.
|
|||
|
|
|||
|
L'humain est ramené à une machine.
|
|||
|
C'est articulation entre "déclencheur contextuel" et "action en réponse de"
|
|||
|
n'est pas une "black box" mais un "feedback" qui, pour l'humain,
|
|||
|
loin d'être ramené à une entrée/sortie, constitue un **feedback**
|
|||
|
utile pour la connaissance de soi.
|
|||
|
|
|||
|
A la place, la communication est ramenée à une **boucle de rétroaction**
|
|||
|
(comme dans un prompt) entre un système comportemental et son environnement.
|
|||
|
La représentation sujet/objet (la perspective traditionnelle) est remplacée
|
|||
|
par le clivage intérieur/extérieur. Behaviorisme, procédural.
|
|||
|
|
|||
|
L'humain est donc ramené à
|
|||
|
|
|||
|
- un ordonnanceur
|
|||
|
- un comportement intrinsèque (boîte noire)
|
|||
|
|
|||
|
- un stimuli pavlovien (déclencheur, trigger) est considéré comme un paramètre
|
|||
|
d'entrée
|
|||
|
- une instruction comportementale est considérée comme une action de
|
|||
|
traitement
|
|||
|
- le résultat est observé.
|
|||
|
|
|||
|
Cette articulation entre "déclencheur contextuel" et "action en réponse"
|
|||
|
est très exactement une forclusion de la profondeur monadique (Leibniz) de
|
|||
|
l'humain à la black box informationnelle (et cybernétique).
|
|||
|
|
|||
|
Pour quoi faire ? Pour pirater. Pour manipuler.
|
|||
|
Le piratage consiste à
|
|||
|
|
|||
|
- isoler les constantes (les procédures répétitives, les algorithmes)
|
|||
|
- les observer (collecter les données)
|
|||
|
|
|||
|
afin de
|
|||
|
|
|||
|
- les réécrire (influence toxique, pishing - hammeçonnage)
|
|||
|
- les détruire (attaque en règle)
|
|||
|
|
|||
|
|
|||
|
Description d'une procédure
|
|||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|||
|
En programmation impérative, un programme est une suite d’instructions qui font
|
|||
|
évoluer l’état mémoire, le résultat est dans l’état final de la mémoire.
|
|||
|
|
|||
|
- une procédure peut prendre des paramètres
|
|||
|
- elle modifie l'état courant du système
|
|||
|
|
|||
|
- Déclaration des paramètes
|
|||
|
- Déclaration du corps
|
|||
|
- Appel de la procédure
|
|||
|
|
|||
|
.. raw:: latex
|
|||
|
|
|||
|
\begin{algorithm}
|
|||
|
\caption{Procédure de permutation de deux entiers}\label{permutation}
|
|||
|
\begin{algorithmic}[1]
|
|||
|
\Procedure{permuter}{$a,b$}{}
|
|||
|
\BState \emph{parametres}:
|
|||
|
\State $a: \textit{int}$
|
|||
|
\State $b: \textit{int}$
|
|||
|
\BState \emph{locales}:
|
|||
|
\State $z: \textit{int}$ \Comment{Une variable intermédiaire est nécessaire}
|
|||
|
\BState \emph{corps}:
|
|||
|
\State $z \gets a$
|
|||
|
\State $a \gets b$
|
|||
|
\State $b \gets z$
|
|||
|
\EndProcedure
|
|||
|
\State \Call{permuter}{10, 12} \Comment{appel de la procédure}
|
|||
|
\end{algorithmic}
|
|||
|
\end{algorithm}
|
|||
|
|
|||
|
effet de bord
|
|||
|
|
|||
|
toute modification de la mémoire ou modification d'un support externe
|
|||
|
|
|||
|
instruction
|
|||
|
|
|||
|
commande ou phrase en mesure de modifier l'état du programme ou de la machine hôte
|
|||
|
(allocation mémoire, support externe, disque, écran...)
|
|||
|
|
|||
|
Une procédure ne renvoie pas de valeur, mais provoque un 'effet de bord' (écriture dans une globale, dans un flux sortant etc.).
|
|||
|
|
|||
|
Une procédure permet de créer une instruction nouvelle qui deviendra une primitive pour le programmeur.
|
|||
|
Cela permet de structurer le texte source du programme et améliorer sa
|
|||
|
lisibilité. Cela permet aussi d'appeler plusieurs fois, et à plusieurs endroit
|
|||
|
dans le code, cette primitive.
|
|||
|
|
|||
|
Appel d'une procédure
|
|||
|
~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|||
|
(ex: pseudo-pascal)
|
|||
|
|
|||
|
**déclaration de procédure**
|
|||
|
|
|||
|
.. raw:: latex
|
|||
|
|
|||
|
\begin{algorithm}
|
|||
|
\caption{Procédure de permutation de deux entiers}\label{appelpermutation}
|
|||
|
\begin{algorithmic}[1]
|
|||
|
\Procedure{permuter}{$a,b$}{}
|
|||
|
\BState \emph{parametres}:
|
|||
|
\State $a: \textit{int}$ \Comment{paramètres formels de la procédure}
|
|||
|
\State $b: \textit{int}$
|
|||
|
\BState \emph{locales}:
|
|||
|
\State $z: \textit{int}$ \Comment{les variables locales de la procédure}
|
|||
|
\BState \emph{corps}:
|
|||
|
\State ... \Comment{Le corps de la procedure}
|
|||
|
\EndProcedure
|
|||
|
\State \Call{permuter}{10, 12} \Comment{l'appel de la procédure}
|
|||
|
\end{algorithmic}
|
|||
|
\end{algorithm}
|
|||
|
|
|||
|
|
|||
|
- les variables x1,...,xn sont appelées *paramètres formels* de p
|
|||
|
- les variables v1,...,vm sont appelées *les variables locales* de p
|
|||
|
|
|||
|
les valeurs effectivement passées en paramètres, ici `10, 12`
|
|||
|
sont appelées **paramètres effectifs** de p
|
|||
|
|
|||
|
signature
|
|||
|
|
|||
|
C'est l'ensemble paramètre formel + resultat de l'appel
|
|||
|
|
|||
|
fermeture
|
|||
|
|
|||
|
L'ensemble procédure + variables locales + signature + parametres effectifs
|
|||
|
est appelé une **fermeture**. C'est la procédure + son contexte qui permet
|
|||
|
de l'instancier dans un programme.
|
|||
|
|
|||
|
Environnement
|
|||
|
|
|||
|
Contexte d’évaluation d'une expression ou d'une fonction
|
|||
|
|
|||
|
Portée
|
|||
|
|
|||
|
La portée d'un identifiant (une variable) est sa condition d'utilisation dans un contexte donné
|
|||
|
(utilisation locale uniquement, ou bien globale, ou bien locale et globale)
|
|||
|
La portée d’une liaison est la portion du code dans laquelle cette
|
|||
|
liaison est valide (i.e. où un identifiant est lié à une expression).
|
|||
|
|
|||
|
.. ifconfig:: exercice
|
|||
|
|
|||
|
**Exercice** : Que donne ce code ?
|
|||
|
|
|||
|
.. code-block:: ocaml
|
|||
|
|
|||
|
# let x = 42 in
|
|||
|
let y = x - 1 in x - y ;;
|
|||
|
|
|||
|
.. ifconfig:: correction
|
|||
|
|
|||
|
**Correction** :
|
|||
|
|
|||
|
.. code-block:: ocaml
|
|||
|
|
|||
|
- : int = 1
|
|||
|
|
|||
|
|
|||
|
.. code-block:: ocaml
|
|||
|
|
|||
|
let a = 3 (* première liaison pour l'identifiant a *)
|
|||
|
let b = 5 and c = 6
|
|||
|
let somme = a + b + c
|
|||
|
val somme : int = 14
|
|||
|
let a = 45 (* deuxième liaison pour l'identifiant a *)
|
|||
|
somme
|
|||
|
val a : int = 45
|
|||
|
|
|||
|
.. ifconfig:: exercice
|
|||
|
|
|||
|
**Exercice** : Que donne ce code ?
|
|||
|
|
|||
|
.. code-block:: ocaml
|
|||
|
|
|||
|
let a = 3 and b = 4 and c = 8 ;;
|
|||
|
let somme = a + b + c ;;
|
|||
|
val somme : int = ???
|
|||
|
let a = 44
|
|||
|
let b = 5
|
|||
|
let c = 1
|
|||
|
somme
|
|||
|
- : int = ???
|
|||
|
|
|||
|
.. ifconfig:: correction
|
|||
|
|
|||
|
.. code-block:: ocaml
|
|||
|
|
|||
|
let a = 3 and b = 4 and c = 8 ;;
|
|||
|
- : int = 15
|
|||
|
let somme = a + b + c ;;
|
|||
|
val somme : int = 15
|
|||
|
let a = 44
|
|||
|
let b = 5
|
|||
|
let c = 1
|
|||
|
somme
|
|||
|
- : int = 15
|
|||
|
|
|||
|
Même code en python
|
|||
|
|
|||
|
.. code-block:: python
|
|||
|
|
|||
|
>>> a = 1
|
|||
|
>>> b = 2
|
|||
|
>>> c = 3
|
|||
|
>>> somme = a + b + c
|
|||
|
>>> somme
|
|||
|
6
|
|||
|
>>> a = 56
|
|||
|
>>> b = 5678
|
|||
|
>>> c = 56789
|
|||
|
>>> somme
|
|||
|
6
|
|||
|
>>>
|
|||
|
|
|||
|
Portée locale dans une expression
|
|||
|
|
|||
|
.. code-block:: ocaml
|
|||
|
|
|||
|
# let a = 2 and b = 3 and c = 4 in
|
|||
|
let somme = a+b+c in
|
|||
|
somme
|
|||
|
- : int = 9
|
|||
|
# somme ;;
|
|||
|
Error: Unbound value somme
|
|||
|
# a ;;
|
|||
|
Error: Unbound value a
|
|||
|
|
|||
|
.. important::
|
|||
|
|
|||
|
L’ordre d’évaluation dans un let ... in ... est bien déterminé,
|
|||
|
sans grande importance dans un cadre purement fonctionnel, mais important
|
|||
|
en cas d’effets de bord
|
|||
|
|
|||
|
|
|||
|
Exemple de variable globale modifiée localement (**attention, mauvaise pratique** !) :
|
|||
|
|
|||
|
.. code-block:: python
|
|||
|
|
|||
|
>>> a = 5
|
|||
|
>>> def print_a():
|
|||
|
... print("La variable a = {0}.".format(a))
|
|||
|
...
|
|||
|
>>> print_a()
|
|||
|
La variable a = 5.
|
|||
|
>>> a = 8
|
|||
|
>>> print_a()
|
|||
|
La variable a = 8.
|
|||
|
>>>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
niveau
|
|||
|
|
|||
|
Le niveau d’une déclaration (de variable ou de procédure) est le nombre
|
|||
|
de procédures sous lesquelles elle est déclarée. Le programme principal
|
|||
|
a le niveau 0.
|
|||
|
|
|||
|
.. code-block:: python
|
|||
|
:linenos:
|
|||
|
|
|||
|
def _get_config(name):
|
|||
|
# return config value
|
|||
|
if not isfile(CONFIG_FILE):
|
|||
|
raise Exception("Fichier de configuration non existant")
|
|||
|
from ConfigParser import ConfigParser
|
|||
|
cfg = ConfigParser(allow_no_value=True)
|
|||
|
cfg.read(CONFIG_FILE)
|
|||
|
|
|||
|
if name == "SUBNETS":
|
|||
|
return eval(cfg.get('eole', 'subnets'))
|
|||
|
elif name == "LEASES_FILE":
|
|||
|
DHCP_PATH = cfg.get('eole', 'container_path_dhcp')
|
|||
|
return join('/', DHCP_PATH, 'var/lib/dhcp/dhcpd.leases')
|
|||
|
def get_routes(*args, **kwargs):
|
|||
|
"""
|
|||
|
Send list of reserved IP
|
|||
|
return list of tuple (id, machine name, IP, MAC Adress)
|
|||
|
"""
|
|||
|
cfg = creole_loader(load_extra=True, rw=False, owner=MODNAME,
|
|||
|
mandatory_permissive=False)
|
|||
|
return zip(cfg.dhcp.dhcp.id_dhcp.id_dhcp, cfg.dhcp.dhcp.id_dhcp.hostname,
|
|||
|
cfg.dhcp.dhcp.id_dhcp.ip, cfg.dhcp.dhcp.id_dhcp.macaddress)
|
|||
|
|
|||
|
On voit que l'objet `cfg` ligne 6 et 7 a le même nom que l'objet `cfg` ligne 19.
|
|||
|
C'est autorisé et les espaces de nommages sont différents.
|
|||
|
|
|||
|
|
|||
|
Description d'une fonction
|
|||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|||
|
Une fonction renvoie une valeur et ne modifie pas l'état courant du programme
|
|||
|
en cours d'exécution ni ne réalise d'effets de bord. Elle renvoie
|
|||
|
**toujours** quelque chose (même la valeur ``None`` qui n'est pas rien)
|
|||
|
|
|||
|
- une procédure peut prendre des paramètres
|
|||
|
- elle modifie l'état courant du système
|
|||
|
|
|||
|
- Déclaration des paramètes
|
|||
|
- Déclaration du corps
|
|||
|
- Appel de la fonction
|
|||
|
|
|||
|
En programmation fonctionnelle, programme est un ensemble de définitions de fonctions,
|
|||
|
un résultat est l'application d’une fonction à une structure de données effective.
|
|||
|
|
|||
|
- composant de base : la fonction
|
|||
|
- opération de base : l’application
|
|||
|
|
|||
|
.. raw:: latex
|
|||
|
|
|||
|
\begin{algorithm}
|
|||
|
\caption{Exemple de fonction}\label{fonction}
|
|||
|
\begin{algorithmic}[1]
|
|||
|
\Function{permuter}{$a,b$}{} \Comment{définition de la fonction}
|
|||
|
\BState \emph{parametres}: \Comment{déclaration (noms, types) des paramètres formels}
|
|||
|
\State $a: \textit{int}$
|
|||
|
\State $b: \textit{int}$
|
|||
|
\BState \emph{locales}: \Comment{déclaration (noms, types) des valeurs locales}
|
|||
|
\State $z: \textit{int}$
|
|||
|
\BState \emph{corps}:
|
|||
|
\State $z \gets a$
|
|||
|
\State $a \gets b$
|
|||
|
\State $b \gets z$
|
|||
|
\BState \emph{return}: \Comment{La valeur, le résulat renvoyé par la fonction}
|
|||
|
\EndFunction
|
|||
|
\State \Call{permuter}{10, 12} \Comment{appel de la fonction}
|
|||
|
\BState \emph{result}:
|
|||
|
\State (12, 10) \Comment{Le résultat effectif de la fonction après appel}
|
|||
|
\end{algorithmic}
|
|||
|
\end{algorithm}
|
|||
|
|
|||
|
.. ifconfig:: exercice
|
|||
|
|
|||
|
**Exercice** : factoriser le code suivant
|
|||
|
::
|
|||
|
|
|||
|
Ecrire "Etes-vous marié ?"
|
|||
|
Rep1 <- ""
|
|||
|
TantQue Rep1 <> "Oui" et Rep1 <> "Non"
|
|||
|
Ecrire "Tapez Oui ou Non"
|
|||
|
Lire Rep1
|
|||
|
FinTantQue
|
|||
|
...
|
|||
|
Ecrire "Avez-vous des enfants ?"
|
|||
|
Rep2 <- ""
|
|||
|
TantQue Rep2 <> "Oui" et Rep2 <> "Non"
|
|||
|
Ecrire "Tapez Oui ou Non"
|
|||
|
Lire Rep2
|
|||
|
FinTantQue
|
|||
|
|
|||
|
.. ifconfig:: correction
|
|||
|
|
|||
|
**Correction** :
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
Fonction RepOuiNon() en caractère
|
|||
|
Truc <- ""
|
|||
|
TantQue Truc <> "Oui" et Truc <> "Non"
|
|||
|
Ecrire "Tapez Oui ou Non"
|
|||
|
Lire Truc
|
|||
|
FinTantQue
|
|||
|
Renvoyer Truc
|
|||
|
Fin
|
|||
|
|
|||
|
Ecrire "Etes-vous marié ?"
|
|||
|
Rep1 <- RepOuiNon()
|
|||
|
...
|
|||
|
Ecrire "Avez-vous des enfants ?"
|
|||
|
Rep2 <- RepOuiNon()
|
|||
|
|
|||
|
|
|||
|
Définition mathématique
|
|||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|||
|
fonction
|
|||
|
|
|||
|
Une fonction f d’un ensemble E vers un ensemble F est une
|
|||
|
correspondance qui associe à chaque élément de E au plus
|
|||
|
un élément de F.
|
|||
|
|
|||
|
- E est appelé le domaine de définition
|
|||
|
- F est appelé codomaine
|
|||
|
- la **signature** de la fonction : `E → F (int -> int = <fun>)`
|
|||
|
|
|||
|
|
|||
|
Exemple de signature d'une fonction
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
Fonction RepOuiNon(Msg en Caractère) en Caractère
|
|||
|
Ecrire Msg
|
|||
|
Truc <- ""
|
|||
|
TantQue Truc <> "Oui" et Truc <> "Non"
|
|||
|
Ecrire "Tapez Oui ou Non"
|
|||
|
Lire Truc
|
|||
|
FinTantQue
|
|||
|
Renvoyer Truc
|
|||
|
Fin Fonction
|
|||
|
|
|||
|
...
|
|||
|
Rep1 <- RepOuiNon("Etes-vous marié ?")
|
|||
|
...
|
|||
|
Rep2 <- RepOuiNon("Avez-vous des enfants ?")
|
|||
|
...
|
|||
|
|
|||
|
|
|||
|
curryfication
|
|||
|
|
|||
|
évaluation de l'application d'une fonction
|
|||
|
|
|||
|
- évaluter `(f x y)`
|
|||
|
- peut donner une **valeur fonctionnelle**
|
|||
|
- évaluation de la valeur fonctionnelle sur une valeur des types de base
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
let g = function n -> (function p -> p + 1) n;;
|
|||
|
|
|||
|
Typage d'une fonction
|
|||
|
~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|||
|
.. code-block:: ocaml
|
|||
|
|
|||
|
# let f x y z = if x > 0 then y + x else z - x;;
|
|||
|
val f : int -> int -> int -> int = <fun>
|
|||
|
|
|||
|
c’est en fait une fonction à un argument qui retourne une fonction::
|
|||
|
|
|||
|
.. code-block:: ocaml
|
|||
|
|
|||
|
val f : int -> (int -> (int -> int)) = <fun>
|
|||
|
|
|||
|
application de f à trois valeurs
|
|||
|
|
|||
|
.. code-block:: ocaml
|
|||
|
|
|||
|
# f 1 2 3;;
|
|||
|
- : int = 3
|
|||
|
|
|||
|
en programmation fonctionnelle,
|
|||
|
les fonctions sont des valeurs comme les autres
|
|||
|
|
|||
|
|
|||
|
.. code-block:: ocaml
|
|||
|
|
|||
|
# fun x -> x * x;;
|
|||
|
- : int -> int = <fun>
|
|||
|
|
|||
|
Récursivité
|
|||
|
~~~~~~~~~~~~
|
|||
|
|
|||
|
.. code-block:: ocaml
|
|||
|
|
|||
|
let rec fact n =
|
|||
|
if n=0 then 1 else n * fact (n-1)
|
|||
|
|
|||
|
|
|||
|
équivalent impératif utilisant une boucle
|
|||
|
|
|||
|
.. code-block:: c
|
|||
|
|
|||
|
int fact(int n){
|
|||
|
int f = 1 ;
|
|||
|
int i = n ;
|
|||
|
while (i>0){
|
|||
|
f = f * i;
|
|||
|
i-- ;
|
|||
|
} ;
|
|||
|
return f ;
|
|||
|
}
|
|||
|
|
|||
|
Définitions par cas
|
|||
|
~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|||
|
.. code-block:: ocaml
|
|||
|
|
|||
|
let rec fact n =
|
|||
|
match n with
|
|||
|
0 -> 1
|
|||
|
| -> n * fact (n-1)
|
|||
|
|
|||
|
**exemple** : la fonction puissance
|
|||
|
|
|||
|
.. code-block:: ocaml
|
|||
|
|
|||
|
let rec puissance x n = match n with
|
|||
|
0 -> 1
|
|||
|
| -> x * puissance x (n-1)
|
|||
|
|
|||
|
|
|||
|
|
|||
|
.. ifconfig:: exercice
|
|||
|
|
|||
|
**Portée locale dans une fonction**
|
|||
|
Quelles sera la valeur de la variable `a` ?
|
|||
|
|
|||
|
.. code-block:: python
|
|||
|
|
|||
|
>>> a = 1
|
|||
|
>>> def myfunc():
|
|||
|
... a = 2
|
|||
|
... return a + 1
|
|||
|
...
|
|||
|
>>> a = myfunc() + a
|
|||
|
|
|||
|
|
|||
|
.. ifconfig:: correction
|
|||
|
|
|||
|
Correction:
|
|||
|
|
|||
|
.. code-block:: python
|
|||
|
|
|||
|
>>> a = 1
|
|||
|
>>> def myfunc():
|
|||
|
... a = 2
|
|||
|
... return a + 1
|
|||
|
...
|
|||
|
>>> a = myfunc() + a
|
|||
|
>>> a
|
|||
|
4
|
|||
|
>>>
|
|||
|
|
|||
|
|
|||
|
.. ifconfig:: exercice
|
|||
|
|
|||
|
**Exercice** : Portée locale dans une fonction avec variable globale
|
|||
|
Quelles sera la valeur de la variable `a` ?
|
|||
|
|
|||
|
.. code-block:: python
|
|||
|
|
|||
|
>>> a = 1
|
|||
|
>>> def myfunc():
|
|||
|
... global a
|
|||
|
... a = 2
|
|||
|
... return a + 1
|
|||
|
...
|
|||
|
>>> a = myfunc() + 3
|
|||
|
>>>
|
|||
|
|
|||
|
.. ifconfig:: correction
|
|||
|
|
|||
|
**Correction** :
|
|||
|
|
|||
|
.. code-block:: python
|
|||
|
|
|||
|
>>> a = 1
|
|||
|
>>> def myfunc():
|
|||
|
... global a
|
|||
|
... a = 2
|
|||
|
... return a + 1
|
|||
|
...
|
|||
|
>>> myfunc()
|
|||
|
3
|
|||
|
>>> a
|
|||
|
2
|
|||
|
>>> a = myfunc() + 3
|
|||
|
>>> a
|
|||
|
6
|
|||
|
>>>
|