Προς το περιεχόμενο

functional programming


tr3quart1sta

Προτεινόμενες αναρτήσεις

Δημοσ. (επεξεργασμένο)

Ανοίγω αυτό το thread, για να γνωρίσουμε και να συζητήσουμε τα περί του συναρτησιακού προγραμματισμού.

Οι περισσότεροι από εσάς που έχετε ασχοληθεί με τον προγραμματισμό, συνήθως δίνετε μία ακολουθία από εντολές οι οποίες θα πρέπει να εκτελεστούνε με μία συγκεκριμένη σειρά. Χρησιμοποιείτε μεταβλητές οι οποίες αποθηκεύουν προσωρινά μια τιμή που πιθανότατα θα αλλάξει κάποια στιγμή. Δημιουργείτε βρόγχους, ώστε να επαναλάβετε ορισμένες εντολές πολλές φορές. Ίσως κατασκευάζετε αντικείμενα, για να δώσετε νέο νόημα στα δεδομένα σας, ακολουθώντας το προγραμματιστικό παράδειγμα του αντικειμενοστρεφούς προγραμματισμού.

Υπάρχουν, όμως, κι άλλες μεθόδοι με τις οποίες μπορεί κανείς να προσεγγίσει ένα υπολογιστικό πρόβλημα. Στον συναρτησιακό προγραμματισμό, λοιπόν, αντί για αντικείμενα στο επίκεντρο βρίσκονται οι μαθηματικές συναρτήσεις. Αυτές οι συναρτήσεις εστιάζουν κυρίως στο "τι" θέλουμε να πετύχουμε και όχι στο "πως" θέλουμε να το πετύχουμε.

Μία μεγάλη διαφορά που μπορούμε να βρούμε στον συναρτησιακό προγραμματισμό, είναι η απουσία μεταβλητών! Δεν υπάρχει, δηλαδή, μία "κατάσταση" στην οποία βρίσκεται το πρόγραμμα που μπορεί να αλλάξει αργότερα. Υπάρχουν μόνο οι συναρτήσεις, που με την είσοδο των ίδιων παραμέτρων θα πρέπει πάντα να επιστρέφουνε το ίδιο αποτέλεσμα, όσες φορές και να εκτελεστούνε και δεν τροποποιούν τίποτα (οι λεγόμενες "pure" συναρτήσεις). Μπορούμε να δηλώσουμε και σταθερές, όπου στην ουσία έχουμε μια συνάρτηση που δεν δέχεται παραμέτρους και επιστρέφει πάντα την ίδια συγκεκριμένη τιμή.

Με την απουσία μεταβλητών παρουσιάζεται ένα μικρό "πρόβλημα" στις δομές επανάληψης, καθώς συνήθως απαιτείται κάποιος μετρητής που θα ορίσει το τέλος της επανάληψης μετά από έναν συγκεκριμένο αριθμό επαναλήψεων. Την λύση έρχεται να μας την προσφέρει η μέθοδος της αναδρομής, όπου τις περισσότερες φορές η συνάρτηση καλεί τον εαυτό της, έως ότου εκπληρωθεί η συνθήκη διακοπής της μεθόδου.

Είναι σύνηθες φαινόμενο στον συναρτησιακό προγραμματισμό, οι συναρτήσεις να δέχονται ως παραμέτρους όχι μόνο συγκεκριμένες τιμές, αλλά και συναρτήσεις! Τέτοιου είδους συναρτήσεις ονομάζονται high order functions. Σε άλλες περιπτώσεις, μπορεί μια συνάρτηση να επιστρέψει μια άλλη συνάρτηση, όπου βλέπουμε το φαινόμενο του partial application.

Τι προτερήματα μπορούμε να λάβουμε από αυτό το είδος προγραμματισμού?

α) Αφού δεν μπορούμε να αλλάξουμε κάποια κατάσταση, σημαίνει ότι το προγραμμά μας δεν έχει "παρενέργειες" (side-effects). Αυτό μας εγγυά λιγότερα σφάλματα στον κώδικα.

β) Ως αποτέλεσμα, η αποσφαλμάτωση του προγράμματος γίνεται πιο εύκολη. Επίσης μπορεί να γίνει και απόδειξη ορθότητας, καθώς μιλάμε για μαθηματικές συναρτήσεις.

γ) Η διαδικασία της παράλληλης εκτέλεσης γίνεται πολύ πιο εύκολη, επειδή τα προβλήματα βρίσκονται πολλές φορές στον συγχρονισμό της πρόσβασης σε μεταβλητές των οποίων η τιμή μπορεί να αλλάξει. Και δεν έχει σημασία η σειρά με την οποία θα εκτελεστούνε οι συναρτήσεις.

δ) Μπορούμε να έχουμε πιο σύντομο και "καθαρό" κώδικα (που επίσης βοηθάει σε πιο ορθό κώδικα). Δείτε ένα παράδειγμα quicksort γραμμένο σε Java και Haskell (pure γλώσσα συναρτησιακού προγραμματισμού) αντίστοιχα:

Sorting a list of numbers: [3,1,4,1,5,9] -> [1,1,3,4,5,9]

Java:

private int[] numbers;

void sort(int[] values) {

  if (values == null || values.length == 0) return;

  numbers = values;

  quicksort(0, values.length -1);

}



void quicksort(int low, int high) {

  int i = low, j = high;

  int pivot = nubers[low];

  while (i <= j) {

    while (numbers[i] < pivot) i++;

    while (numbers[j] > pivot) j--;

    if (i <= j) { exchange(i,j); i++; j--; }

  }

  if (low < j) quicksort(low, j);

  if (i < high) quicksort(i, high);

}



void exchange(int i, int j) {

  int temp = numbers[i];

  numbers[i] = numbers[j];

  numbers[j] = temp;

}

Haskell:

sort[] = []

sort(x:xs) = sort ys ++ [x] ++ sort zs

  where ys = [y | y <- xs, y <= x]

        zs = [z | z <- xs, x < z]

ε) Γίνεται πιο εύκολη η διάσπαση του προγράμματος σε modules, όπου έχουμε ομάδες συναρτήσεων που ανήκουνε στην ίδια κατηγορία.

Ακόμα και αν κάποιος δεν ασχοληθεί με μία καθαρά συναρτησιακή γλώσσα προγραμματισμού, βλέπουμε άλλες γνωστές γλώσσες να υιοθετούν στοιχεία και χαρακτηριστικά από αυτό το είδος. Για παράδειγμα βλέπουμε η Java στην έκδοση 8 να εισάγει τα λεγόμενα lambda expressions. Στην ουσία πρόκειται για ανώνυμες συναρτήσεις που πολλές φορές γράφονται απευθείας μέσα στην παράμετρο κάποιας άλλης συνάρτησης. Ακόμα και η κυρίαρχη γλώσσα στο Web, η Javascript προσφέρει παρόμοια λειτουργικότητα. Ένα παράδειγμα σε κώδικα:
 

function say(word) {

  console.log(word);

}



function execute(someFunction, value) {

  someFunction(value);

}



execute(say, "Hello");

Μπορούμε να το γράψουμε και με άλλο τρόπο:
 

function execute(someFunction, value) {

  someFunction(value);

}



execute(function(word){ console.log(word) }, "Hello");

Με αυτόν τον τρόπο, προσφέρουμε μια επιπλέον ευελιξία στον κώδικα, καθώς η λειτουργικότητα μιας συνάρτησης μπορεί να αλλάξει δυναμικά ανάλογα με ποιά ή ποιές άλλες συναρτήσεις δέχεται ως είσοδο!

Πιστεύω ότι ο συναρτησιακός προγραμματισμός είναι μία από τις πιό ενδιαφέρουσες κατηγορίες προγραμματισμού και είναι must για κάθε developer να γνωρίζει παραδείγματα από διάφορες μορφές προγραμματισμού.

Για όποιον ενδιαφέρεται για παραπάνω πληροφορίες, μπορεί να τις βρεί σε μερικά από τα παρακάτω links:

Functional Programming is black magic.

 

Ένα πολύ καλό tutorial

Ένα βιβλίο

Ένα σύντομο διαδραστικό online tutorial

Εν συντωμία

Μάθημα στα ελληνικά!

Για τυχόν λάθη ή σημαντικά σημεία που ίσως να ξέχασα, πείτε μου να τα σημπληρώσω. Φυσικά περιμένω και τις δικές σας απόψεις πάνω στο functional programming!

 

Edit: Να σημειώσω ότι ο συναρτησιακός προγραμματισμός δεν είναι κατάλληλος για να επιλυθούν πάσης φύσεως προβλήματα, καθώς σε πολλές περιπτώσεις υπάρχουν καταλληλότεροι (μη-συναρτησιακοί) τρόποι για να γίνει αυτό. Το functional programming φέρνει μαζί του τα δικά του μειονεκτήματα, όμως είναι εδώ για να προσφέρει στον προγραμματιστή ένα νέο τρόπο σκέψης, από τον οποίο μπορεί να επωφεληθεί συνδυάζοντάς τον με τον υπάρχων τρόπο δουλειάς του.

Επεξ/σία από tr3quart1sta
  • Like 16
  • Thanks 1
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

  • Απαντ. 85
  • Δημ.
  • Τελ. απάντηση

Συχνή συμμετοχή στο θέμα

Συχνή συμμετοχή στο θέμα

Από τις πιο καλές εισαγωγές στο functional programming που έχω διαβάσει!

Ήμουν έτοιμος να βάλω link για το tryhaskell.org, όταν είδα ότι το συμπεριέλαβες.

 

Νομίζω ότι, ειδικά στην Ελλάδα, το functional programming χρησιμοποιείται ακόμα σαν hobby, ή τελείως "ακαδημαϊκά".

Παλιότερα, όταν ήθελα να κάνω κάτι διαφορετικό από την πρωινή μου δουλειά, το έριχνα στην haskell (και λίγο lisp τότε...). Ένοιωθα ότι μου ακονίζει το μυαλό.

 

Είναι που λένε, "από δω η γυναίκα μου κι από δω το αίσθημά μου" :D

  • Like 3
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δεν ξέρω κατά πόσο η αποσφαλμάτωση γίνεται πιο εύκολη η δύσκολη.

Πιστεύω έγκειται στο πως γράφεις, τι μεθοδολογίες χρησιμοποιείς και κατά πόσο είσαι εξοικειωμένος με αυτές.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Το JQuery δεν είναι γλώσσα, είναι framework. Η γλώσσα από κάτω είναι η javascript (το ξέρω ότι το ξέρεις, αλλά το λέω για τους περαστικούς...).

Η javascript δεν είναι functional σε καμία περίπτωση.

Άρα; B)

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δεν ξέρω κατά πόσο η αποσφαλμάτωση γίνεται πιο εύκολη η δύσκολη.

Πιστεύω έγκειται στο πως γράφεις, τι μεθοδολογίες χρησιμοποιείς και κατά πόσο είσαι εξοικειωμένος με αυτές.

 

"What you need to know is that purity makes your program work. When you write impure functions, which is literally every single piece of code you have ever written in OO style, you are creating problems. If your code is impure, its behavior depends on stuff that could be going on 100s of lines away or long before it is called. If your code is pure, all you need to know is what is going on inside the function."

 

Συν το ότι έχεις λιγότερο και πιο "καθαρό" κώδικα, πιστεύω οδηγεί σε λιγότερα λάθη.

 

 

Το JQuery δεν είναι γλώσσα, είναι framework. Η γλώσσα από κάτω είναι η javascript (το ξέρω ότι το ξέρεις, αλλά το λέω για τους περαστικούς...).

Η javascript δεν είναι functional σε καμία περίπτωση.

Άρα; B)

 

Πολλές σύγχρονες γλώσσες είναι multi-paradigm. Συγκεκριμένα για την javascript: scripting, object-oriented (prototype-based), imperative, functional

 

Οπότε, εν μέρει είναι *και* functional. Γι'αυτό έχει τα οφέλη του, το να ασχοληθεί κάποιος με την λειτουργικότητα του functional, αφού κατά πάσα πιθανότητα θα συναντήσει μερικά από αυτά τα χαρακτηριστικά σε πολλές γλώσσες. (Αλλά εσύ μπορεί να εννοούσες ότι η javascript δεν είναι "καθαρά" functional, το οποίο φυσικά και ισχύει.)

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Πολλές σύγχρονες γλώσσες είναι multi-paradigm. Συγκεκριμένα για την javascript: scripting, object-oriented (prototype-based), imperative, functional

 

Οπότε, εν μέρει είναι *και* functional. Γι'αυτό έχει τα οφέλη του, το να ασχοληθεί κάποιος με την λειτουργικότητα του functional, αφού κατά πάσα πιθανότητα θα συναντήσει μερικά από αυτά τα χαρακτηριστικά σε πολλές γλώσσες. (Αλλά εσύ μπορεί να εννοούσες ότι η javascript δεν είναι "καθαρά" functional, το οποίο φυσικά και ισχύει.)

 

Μulti-paradigm, ναι. Και παρεμπιπτόντως, μου αρέσει και ο διαχωρισμός του prototype-based στην παρένθεση, που σημαίνει ότι είναι oo κατά μία έννοια, κι όχι κατά την κλασσική θεώρηση. Καλό είναι να βάζουμε τα πράγματα σε σωστό πλαίσιο.

 

Αυτό όμως δεν την κάνει ούτε functional ούτε oo. Την κάνει jack-of-all-trades. Και τα καταφέρνει καλά εκεί, δε λέω. Αλλά για την κατηγοριοποίηση, είναι μία μίξη με "λίγο απ' όλα".

 

Ειδικότερα για το functional μέρος, απέχει πολύ. Έχει flow, μεταβλητές, state. Στο καθαρό functional programming δεν προσδιορίζεις σειρά εκτέλεσης αλλά τρόπο υπολογισμού. Δηλαδή, όχι τι και με ποια σειρά, αλλά πως.

 

Είναι σαν να λες ότι ένα supermarket είναι κρεοπωλείο. Όχι δεν είναι, απλά πουλάει και κρέατα. Μεταξύ άλλων.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Θα το'λεγα οο & functional

 

Οϊ. Δεν είναι τίποτα απ' αυτά αν και ίσως επιφανειακά να μοιάζει λίγο (το ίδιο ισχύει και για το χαρακτηρισμό του ως monad). Δεν είναι functional ούτε το ίδιο (προφανές βλέποντας το source) ούτε και σε αναγκάζει ή έστω σε οδηγεί στο να γράφεις functional κώδικα. Θα μπορούσε να κάνει το τελευταίο, στην οποία περίπτωση ίσως να ταίριαζε ο χαρακτηρισμός, αλλά αυτό θα ήταν πολύ άσχημη engineering επιλογή αν λάβεις υπόψη πρακτικούς παράγοντες όπως το σε τι στοχεύει και σε τι περιβάλλον τρέχει.

 

Το JQuery δεν είναι γλώσσα, είναι framework. Η γλώσσα από κάτω είναι η javascript (το ξέρω ότι το ξέρεις, αλλά το λέω για τους περαστικούς...).

Η javascript δεν είναι functional σε καμία περίπτωση.

Άρα; B)

 

Θα μπορούσε σαν framework να σου δίνει ένα functional μόνο interface να χρησιμοποιήσεις, στην οποία περίπτωση θα ταίριαζε να το πεις functional. Όπως ήδη είπατε αυτό είναι καθ' όλα δυνατό σε JS.

 

"What you need to know is that purity makes your program work. When you write impure functions, which is literally every single piece of code you have ever written in OO style, you are creating problems. If your code is impure, its behavior depends on stuff that could be going on 100s of lines away or long before it is called. If your code is pure, all you need to know is what is going on inside the function."

 

Συν το ότι έχεις λιγότερο και πιο "καθαρό" κώδικα, πιστεύω οδηγεί σε λιγότερα λάθη.

 

Το μόνο πράγμα που μου χτύπησε αρνητικά στην παρουσίαση που έχεις κάνει στο thread, και το λέω τώρα γιατί εδώ το ξανακάνεις, είναι πως περνάει (υποθέτω ακόμα περισσότερο σε κάποιον που δεν έχει ήδη εμπειρία) την αίσθηση πως το functional είναι μαγική νεραϊδόσκονη με την οποία λύνονται όλα ή τέλος πάντων πολλά από τα προβλήματα που αντιμετωπίζει κανείς γράφοντας software.

 

Είμαι βέβαιος πως δεν το εννοείς έτσι, αλλά αυτό ακριβώς αφήνουν να εννοηθεί quotes σαν το παραπάνω. Ναι, you just need to know what's going on inside the function, και επίσης you need to know ότι αυτό το purity το πληρώνεις με πλήθος πρακτικών παραχωρήσεων. Αν αυτό που πας να κάνεις όντως θα ωφεληθεί από τα πλεονεκτήματα μιας π.χ. no-mutable-state προσέγγισης τότε τέλεια, αν όμως όχι τότε απλά πληρώνεις τα tradeoffs για να κερδίσεις πράγματα που θα μείνουν στο ράφι.

 

Επίσης πρέπει να λάβεις υπόψη ότι από τα προβλήματα που καλούνται να λύσουν οι προγραμματιστές ανα τον κόσμο, ένα πολύ μικρό μόνο μέρος (μετρώντας με "πόσα κεφάλια ασχολούνται μ' αυτό") είναι ταιριαστό με functional προσεγγίσεις. Για όλους τους υπόλοιπους είτε είσαι Paul Graham και κάνεις ο,τι σου αρέσει είτε δοκιμάζεις "functional με το ζόρι" και τελικά καταλήγεις να κάνεις περισσότερα λάθη επειδή η functional προσέγγιση απαιτεί πολύ μεγαλύτερα επίπεδα αφηρημένης σκέψης κλπ.

 

Anyway, TL;DR: Το functional είναι φανταστικό και σου ανοίγει το μυαλό όταν έρθεις σ' επαφή μαζί του, αλλά δεν είναι πανάκεια ούτε πάει με όλα όπως η φέτα. Είναι πιο σημαντικό η δουλειά να γίνεται και ο μάστορας να δουλεύει με τα εργαλεία που θα δώσουν το καλύτερο πρακτικό αποτέλεσμα, aka "huge practical challenges".

  • Like 4
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Το μόνο πράγμα που μου χτύπησε αρνητικά στην παρουσίαση που έχεις κάνει στο thread, και το λέω τώρα γιατί εδώ το ξανακάνεις, είναι πως περνάει (υποθέτω ακόμα περισσότερο σε κάποιον που δεν έχει ήδη εμπειρία) την αίσθηση πως το functional είναι μαγική νεραϊδόσκονη με την οποία λύνονται όλα ή τέλος πάντων πολλά από τα προβλήματα που αντιμετωπίζει κανείς γράφοντας software.

 

Ευχαριστώ πολύ για το feedback, έκανα edit το αρχικό post. ;)

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Παντως οι τεσσερις γραμμες της haskell δεν ειναι quicksort.

http://stackoverflow.com/questions/7717691/why-is-the-minimalist-example-haskell-quicksort-not-a-true-quicksort

 

Επισης καμια procedural ή oo γλωσσα δεν σε υποχρεώνει να εχεις "κατασταση" (local, global variables ή fields σε oop). Μπορεις να γραφεις καθαρα "μαθηματικές" συναρτήσεις.

Μπορω να κανω αναδρομη στην C/C++ αλλα  εχω και την επιλογη loop.

Αυτο σαν περιορισμός μου φενεται στις συναρτησιακες.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Παντως οι τεσσερις γραμμες της haskell δεν ειναι quicksort.

http://stackoverflow.com/questions/7717691/why-is-the-minimalist-example-haskell-quicksort-not-a-true-quicksort

 

Το debate για τη συγκεκριμένη υλοποίηση πάει πολλάαααα χρόνια πίσω...

Η εντύπωση που έχω αποκομίσει εγώ είναι ότι η αρχική δημοσίευση του "αλγόριθμου" στο site της Haskell, παράλληλα με αυτόν σε C, ήταν για να δείξουν τον τρόπο γραφής, σε αντιπαραβολή με κάτι στο οποίο ο αναγνώστης ήταν ήδη εξοικειωμένος.

 

Ούτως ή άλλως, δεν νομίζω να έχει νόημα να μιλάμε για αλγόριθμους σε συναρτησιακές γλώσσες. Δεν υπάρχει αυτή η έννοια, τουλάχιστον κατά τη Von Neumann θεώρησή του. Ο quicksort είναι ένας αλγόριθμος, που μάλιστα έχει νόημα να τον συγκρίνουμε ως προς τις επιδόσεις με άλλους ισοδύναμους. Σε μία συναρτησιακή γλώσσα δεν θα είχε ποτέ νόημα (θεωρητικά) να μιλάμε για επίδοση. Δεν θα χρειαζόταν να μιλάμε για quicksort, bubblesort ή mergesort, αλλά απλά και μόνο για sort.

 

Όπως ειπώθηκε παραπάνω:

 

...η functional προσέγγιση απαιτεί πολύ μεγαλύτερα επίπεδα αφηρημένης σκέψης...

Επισης καμια procedural ή oo γλωσσα δεν σε υποχρεώνει να εχεις "κατασταση" (local, global variables ή fields σε oop). Μπορεις να γραφεις καθαρα "μαθηματικές" συναρτήσεις.

Μπορω να κανω αναδρομη στην C/C++ αλλα  εχω και την επιλογη loop.

Αυτο σαν περιορισμός μου φενεται στις συναρτησιακες.

 

Όλες οι procedural γλώσσες σε υποχρεώνουν να έχεις κατάσταση, με τον ένα ή τον άλλο τρόπο. Αν χρησιμοποιήσεις αναδρομή, θα έχεις και ένα if για να την τερματίσεις. Αυτό αμέσως-αμέσως είναι flow control, που υπονοεί state, έτσι δεν είναι;

 

Άλλωστε, δεν είπε κανείς ότι δεν θα μπορούσε μία γλώσσα να περιέχει στοιχεία functional (βλ. και javascript παραπάνω). Δεν είναι  functional η γλώσσα που σε αφήνει απλά να γράφεις functional, αλλά αυτή στην οποία μπορείς να γράφεις μόνο έτσι.

 

Και ναι, είναι περιορισμός. Και δεν είναι ο μόνος. Κανείς δεν είπε το αντίθετο, νομίζω.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Να πω κι εγώ την γνώμη μου, σαν αρχάριος.

 

Έχω ασχοληθεί μόνο με την SML. Είμαι fan των procedural και πίστευα πως είχα μία κάποια ιδέα πάνω στην αναδρομή, αλλά μόλις συνάντησα την SML, κατάλαβα πως έπρεπε να είμαι λίγο πιο προσεκτικός. Σε κάνει να σκέφτεσαι! Βέβαια δεν είναι και τόσο δύσκολο να προσαρμοστείς στην SML αν έχεις κάποια εμπειρία με μια procedural. Το δύσκολο είναι πιστεύω να προσαρμοστείς σε μία γλώσσα Λογικου Προγραμματισμού, όπως είναι η Prolog. Η Prolog, από τη μικρή μέχρι τώρα εμπειρία μου είναι ο ορισμός της αναδρομής. Η Prolog θέλει σκέψη, όχι αστεία. Προβλήματα που λύνεις με την SML γρήγορα, στη Prolog είναι σπαζοκεφαλιές. Λογικά η διαφορά αυτή οφείλεται στο γεγονός ότι η Prolog είναι ακόμη πιο λιτή και έχει μία ολόκληρη θεωρία από πίσω της. Κατά την γνώμη μου, το γεγονός ότι είναι λιτή, αποτελεί και προτέρημα. Ακόμη λιγότερα συντακτικά λάθη.

 

 

(Γράφω εδώ γιατί βγαίνω από το θέμα)Ωστόσο έχω την επιθυμία να συνεχίσω να μαθαίνω μία από τις 2 γλώσσες ή ακόμη καλύτερα, ιδέες (functional or logical). Επειδή όμως δεν γνωρίζω τί παίζει εκεί έξω, ξέρετε να μου πείτε τί χρησιμοποιείται σήμερα και που θα ήταν καλύτερα να εφιστήσω την προσοχή μου;

 

Ευχαριστώ!

 

 

  • Like 1
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα

  • Δημιουργία νέου...