Czy F# jest językiem funkcyjnym czy obiektowo-funkcyjnym?

1

Czy to że List w F# implementuje interfejsy jest wystarczającym dowodem na to żeby uzznać że F# jest językiem obiektowym?
Takie pytanie się narodziło w mojej głowie

Początek deklaracji typu List

type List<'T> = 
       | ([])  :                  'T list
       | ( :: )  : Head: 'T * Tail: 'T list -> 'T list
       interface System.Collections.Generic.IEnumerable<'T>
       interface System.Collections.IEnumerable
       interface System.Collections.Generic.IReadOnlyCollection<'T>
       interface System.Collections.Generic.IReadOnlyList<'T>

źródło
Dokumentacja

1

Wg. mojej najlepszej wiedzy jest najczęściej wykorzystywany jako funkcyjny, choć obiektowo pisać też się da. Pierwszy znaleziony wynik:
https://fsharpforfunandprofit.com/posts/object-oriented-intro/

1

Nie trzeba ankiety ;)

https://pl.wikipedia.org/wiki/Programowanie_wieloparadygmatowe

F# – wieloparadygmatowy język programowania zawierający w sobie głównie cechy języka funkcyjnego, ale umożliwiającym także pisanie kodu imperatywnego oraz obiektowego.

0

Jest spora różnica między fuknkcyjno-obiektowy, a trzeba używać obiektów.
Jest wiele języków imperatywnych, w których np. wcale nie trzeba używać pętli.

3

Tak btw - są w F# algebraiczne struktury danych jak w Haskellu czy nawet już Javie?

1
somekind napisał(a):

Jest spora różnica między fuknkcyjno-obiektowy, a trzeba używać obiektów.

To proste pytanie. Klasyczny przykład polimorfizmu w OO:

[<AbstractClass>]
type XmasCharacter() =
    abstract member Speak: unit -> unit

type SantaClaus() =
    inherit XmasCharacter()
    override this.Speak() = printfn "Ho Ho Ho!"
    
type DedMoroz() =
    inherit XmasCharacter()
    override this.Speak() = printfn "Xo Xo Xo!"

// create Santa, then cast the reference to XmasCharacter type
let grandpa = SantaClaus() :> XmasCharacter 
grandpa.Speak() // prints Ho Ho Ho!

Jak zrobić to w F# bez obiektów skoro nie trzeba używać obiektów?

0

Dobra, za radą @Wibowit dodałem stan:

[<AbstractClass>]
type XmasCharacter() =
    abstract member Speak: unit -> unit

type SantaClaus(x: int) =
    inherit XmasCharacter()
    override this.Speak() = printfn "Ho Ho Ho %i!" x 

type DedMoroz(x: string) =
    inherit XmasCharacter()
    override this.Speak() = printfn "Xo Xo Xo %s!" x

// create Santa, then cast the reference to XmasCharacter type
let grandpa = SantaClaus(666) :> XmasCharacter 
grandpa.Speak() // prints Ho Ho Ho 666!

@somekind Jak zrobić to bez obiektów/klas w F# ?

Wersja z ToString :P

[<AbstractClass>]
type XmasCharacter() =
    abstract member Speak: unit -> unit

type SantaClaus(x: int) =
    inherit XmasCharacter()
    override this.Speak() =
        let s = "Ho Ho Ho " + x.ToString()
        printfn "%s" s

type DedMoroz(x: string) =
    inherit XmasCharacter()
    override this.Speak() =
        let s = "Xo Xo Xo " + x.ToString()
        printfn "%s" s

// create Santa, then cast the reference to XmasCharacter type
let grandpa = SantaClaus(666) :> XmasCharacter 
grandpa.Speak() // prints Ho Ho Ho 666
0

@stivens: Specjalnie dla ciebie - mutowalny stan:

[<AbstractClass>]
type XmasCharacter() =
    abstract member Speak: unit -> unit

type SantaClaus(x: int) =
    inherit XmasCharacter()
    let mutable x = x
    member this.SetX value =
        x <- value
    override this.Speak() =
        let s = "Ho Ho Ho " + x.ToString()
        printfn "%s" s

type DedMoroz(x: string) =
    inherit XmasCharacter()
    let mutable x = x
    member this.SetX value =
        x <- value
    override this.Speak() =
        let s = "Xo Xo Xo " + x.ToString()
        printfn "%s" s

// create Santa, then cast the reference to XmasCharacter type

let santa = SantaClaus(666)

let grandpa = santa:> XmasCharacter 
grandpa.Speak() // prints Ho Ho Ho 666

santa.SetX(777) 

grandpa.Speak() // prints Ho Ho Ho 777

Jeszcze chwila i zacznę wysyłać CV na programistę F#

1

@scibi_92: Przykład Polimorfizmu w Haskellu na TypeClassach, z oczywistych powodów pominę mutowalny stan :P

main = putStrLn (speak grandpa)
  where grandpa = SantaClaus 666

--  tu definiujemy typy
newtype SantaClaus = SantaClaus { unSantaClaus :: Int }
newtype DedMoroz = DedMoroz { unDedMoroz :: String }

-- tu definiujemy "interfejs"
class XmasCharacter a where
  -- sygnatura metody
  speak :: a -> String

-- tu definiujemy implementacje
instance XmasCharacter SantaClaus where
  -- ciało metody
  speak a = "Ho Ho Ho " ++ show (unSantaClaus a) -- znów mamy `show` czyli Haskellowy odpowiednik `toString`

instance XmasCharacter DedMoroz where
  -- ciało metody
  speak a = "Xo Xo Xo " ++ unDedMoroz a
2

Dobra, znalazłem przykład ze Scali

0
KamilAdam napisał(a):

To proste pytanie. Klasyczny przykład polimorfizmu w OO:

W moim mniemaniu proste pytanie zadałem ja, w komentarzach do wiadomego wątku. Odpowiedzi nie otrzymałem.

Jak zrobić to w F# bez obiektów skoro nie trzeba używać obiektów?

Nie bardzo rozumiem, co próbujesz udowodnić ani czemu mnie o to pytasz. Masz problem z rozumieniem czasowników modalnych?
Piszesz sobie obiektowy kod w języku, który to umożliwia, bo chcesz i możesz. Z tego nie wynika, że w ogólności cokolwiek trzeba, a jedynie tyle, że dobrałeś taki przykład.

1

Jest obiektowy praktycznie tak jak każdy inny współczesny język. OOP jest tak pustym sloganem, bo w zasadzie ciężko powiedzieć czym programowanie obiektowe jest. Dla mnie OOP to połączenie zachowań z stanem, bo praktycznie każda inna cecha (enkapsulacja czy polimorfizm) nie wywodzi się bezpośrednio z tradycji języków obiektowych. A miks dane <-> zachowania jest tak fundamentalny, że widzimy go zarówno w assemblerze jak i w haskellu.

Dla mnie klasyfikacja powinna być bardziej drobnoziarnista tj. język A wspiera subclass polymorphism a język B wspiera type classy

3
slsy napisał(a):

A miks dane <-> zachowania jest tak fundamentalny, że widzimy go zarówno w assemblerze jak i w haskellu.

Kodziłem zarówno w asemblerze (dużo) jak i w Haskellu (mało) i tam obiektówki raczej nie ma (no chyba, że jak robiłem okienka za pomocą win32api). Obiekt jest wtedy, kiedy za pomocą jednego uchwytu (czyli w praktyce referencji) możemy mieć dostęp zarówno do danych jak i funkcji na nich operujących.

Poniższy kod nie jest obiektowy, bo przetwarzajDane nie jest składową typu dane:

var dane = posklejajDane(arg1, arg2, arg3, ..., argN);
przetwarzajDane(dane);

Poniższy kod jest obiektowy jeżeli metoda przetwarzaj jest polimorficzna (czyli de facto wirtualna):

var dane = posklejajDane(arg1, arg2, arg3, ..., argN);
dane.przetwarzaj();

Gdyby w powyższym kodzie metoda przetwarzaj była metodą rozszerzającą czy też inną całkowicie statycznie wiązaną metodą to byłoby trochę dyskusyjne czy to bardziej OOP czy może cukier składniowy wyglądający jak OOP. Generalnie zapis xyz.abc() sugeruje prawie wszystkim obiektówkę, a zapis abc(xyz) kod nieobiektowy, ale to moim zdaniem zbytnie spłycanie paradygmatu obiektowego.

Rust ma typeclassy (które akurat w tym języku definiuje się za pomocą słówka kluczowego trait, a nie class jak w Haskellu), ale zamiast typowego dla Haskella zapisu np. sort list oferuje zapis list.sort(). Mimo iż ten zapis z Rusta wygląda jak obiektowy (dla kogoś kto kieruje się powierzchownością, czyli dla zdecydowanej większości programistów) to nadal (pomijając https://doc.rust-lang.org/reference/types/trait-object.html ) pozostaje nieobiektowy, bo opiera się na typeclassach (przy założeniu, że to sort jest polimorficzną metodą, czyli właśnie z typeclassy).

Typeclassy mają sens generalnie tylko przy językach statycznie typowanych (chodzi o sprawdzanie typów na etapie kompilacji, a reified generics nie mają tu znaczenia). Jednym z założeń typeclasses jest to, że się ich jawnie nie podaje (w sensie ich instancji). Zamiast tego to kompilator, na podstawie statycznie obliczonego typu, sam wyszukuje instancje typeclass i podaje je tam gdzie są potrzebne. Dzięki temu kod wygląda jakby struktury danych magicznie nabywały wbudowanych w nie metod, ale tak nie jest. Typeclassy są zadeklarowane (chodzi o interfejs) i zdefiniowane (chodzi o instancje) poza strukturami danych, więc nie ma OOPowego łączenia stanu i zachowania w jeden byt.

0

@Wibowit: ale tutaj mówisz o polimorfizmie. Czy jeśli z Javy usuniemy wszystkie mechanizmy z tym związane (klasa Object, dziedziczenie, interfejsy i tak dalej) to zostanie ona językiem OOP czy nie?

0

Hm, co zostanie? Klasy z danymi i niepolimorficznymi metodami. Bardzo łatwo zamienić to na klasy z danymi i statycznymi metodami. Różnica będzie tylko w zapisie. dane.metoda() zamieni się na metoda(dane).
Czy to jeszcze jest programowanie obiektowe?

0

@KamilAdam: Dobre pytanie, sam nie wiem. Inna sprawa, że np. przy kwalifikacji imperatywne vs funkcyjne nie ma takiego problemu: kryteria są proste i łatwo powiedzieć na postawie kodu czy jest on imperatywny, funkcyjny czy oba na raz

1

Nie ma czegoś takiego jak język obiektowo-funkcyjny.

Jeśli w języku istnieje assignment, np można zrobić coś takiego i = 2; i = 3; to nie jest i nie może być funkcyjny. Można w nim napisać funkcyjny kod, po prostu nie używając assignmentów; ale język funkcyjny nie jest, przez to że to dopuszcza. Jeśli natomiast nie ma przypisania, to nie specjalnie można konstrukować obiekty i układać je w hierarchię , więc nie ma mowy żeby był obiektowy.

W skrócie, idea języka "paradygmato-paradygmatowego" nie może istnieć. To jakby powiedzieć że restauracja jest "wegetariańsko-mięsna" albo zadanie jest "łatwo-trudne", ewentualnie ktoś oddał do serwisu telefon "zepsuto-działający".

3

@slsy + @KamilAdam
jeśli z javy usuniemy extends (dziedziczenie) i implements (interfejsy), ale zostawimy statyczne typowanie, to zostaniemy z czymś co można nazwać co najwyżej mocno zdegenerowaną obiektowością. nota bene rust oferuje składnię typu zmienna.róbcoś(parametry) (obojętne czy z typeclassy czy nie) oraz oferuje enkapsulację (https://rust-lang.github.io/rfcs/0001-private-fields.html), a więc kierując się samymi tymi aspektami wychodziłoby, że rust jest obiektowy (a generalnie nie jest za taki uważany).

próbowałem sklecić jakąś własną tyradę o esencji obiektowości, ale kręcę się w kółko :) ogólnie ograniczenie się całkowicie do statycznego wiązania sprawia (moim zdaniem), że OOP się mocno degeneruje (teoretycznie i praktycznie). ponadto wikipedia ma ciekawy, chociaż dziwny, podpunkt sugerujący, że OOP mocno polega na dynamicznym wiązaniu:
https://en.wikipedia.org/wiki/Object-oriented_programming#Dynamic_dispatch/message_passing

Dynamic dispatch/message passing

It is the responsibility of the object, not any external code, to select the procedural code to execute in response to a method call, typically by looking up the method at run time in a table associated with the object. This feature is known as dynamic dispatch, and distinguishes an object from an abstract data type (or module), which has a fixed (static) implementation of the operations for all instances. If the call variability relies on more than the single type of the object on which it is called (i.e. at least one other parameter object is involved in the method choice), one speaks of multiple dispatch.

A method call is also known as message passing. It is conceptualized as a message (the name of the method and its input parameters) being passed to the object for dispatch.

0

No bez międzymordzia i dziedziczenia w Javie nie ma polimorfizmu więc to wiadomo co a nie OOP byłoby ;]

0
jarekr000000napisał(a):

Praktycznie wszystkie jezyki funkcyjne to po prostu podzbiory istniejących języków wieloparadygmatowych. Funkcyjna scala, funkcyjny haskell, funkcyjny PHP.

Można sobie nawet taki język ładnie skonfigurować korzystając z lintera: tu mój linter, który praktycznie zmusza do pisania czysto funkcyjnego w kotlinie: https://github.com/neeffect/kure-potlin

Mówiłem już. Możesz napisać 100% funkcyjny kod w niefunkcyjnym języku. Nie znaczy to że język jest funkcyjny. Podobnie jak możesz napisać obiektowy kod w nieobiwktowym języku, co nie oznacza że język jest obiektowy. Przykład dla nie-programistów: można zjeść wegetariańskie danie w restauracji, co nie oznacza że restauracja jest wegetariańska.

Funkcyjny język to taki w którym można pisać tylko funkcyjnie, ponieważ język sam to wymusza.

Przy czym od razu wyjaśniam że pojedyncze przypisanie jest totalnie okej. Np w haskellu można przypisać wartośc do zmiennej, let i = 2, ale to tyle.

0
slsy napisał(a):

Co do paradygmatów to nie masz racji, bo paradygmaty to nie występują w przeciwnych parach. Programowanie obiektowe to zupełnie inna dziedzina, nic funkcyjność/imperatywność. Chyba nie powiesz, że Java nie należy do paradygmatu imperatywnego

Ja bym powiedział że tzw. "języki hybrydowe" nie należą do żadnego paradygmatu. Skoro można w nich pisać kod taki, taki i sraki, to o żadnym paradygmacie nie może być mowy. Dodawajcie sobie lintery do nich i sprawdzajcie to, wtedy wasze programy mogą się stać obiektowe, funkcyjne, proceduralne lub strukturalne, ale to nie znaczy że język taki jest.

4
TomRiddle napisał(a):

Funkcyjny język to taki w którym można pisać tylko funkcyjnie, ponieważ język sam to wymusza.

Dobra zasada, podpatrzona u matematyków, jest taka, że po takim określeniu zwykle warto podać choć jeden element należący do zbioru. Czy znasz taki język (funkcyjny)?

0
jarekr000000 napisał(a):
TomRiddle napisał(a):

Funkcyjny język to taki w którym można pisać tylko funkcyjnie, ponieważ język sam to wymusza.

Dobra zasada, podpatrzona u matematyków, jest taka, że po takim określeniu zwykle warto podać choć jeden element należący do zbioru. Czy znasz taki język (funkcyjny)?

No dla mnie dobrą zasadą kciuka jest to, że jeśli w języku jest assignment zmiennych, np taki i = 2; i = 3; to to nie jest język funkcyjny.

Przy czym od razu mówię że pojedyncze przypisanie, jak np let i = 2 w haskellu, gdzie nie można zadeklarować niezainicjalizowanej zmiennej jest funckyjne imo. To przypisanie ponowne, zmiana czegoś nie jest. Bo w sumie takie zadeklarowania i przypisanie się różni prawie niczym od zdefiniowania funkcji która zwraca stałą.

2

@TomRiddle:
Najpierw rzucasz całkiem ostre definicje, a potem zmieniasz w jakieś reguły kciuka...

Pogrążamy haskell.

import Data.IORef
import System.IO.Unsafe (unsafePerformIO)
-- zmienna globalna  (!!!) `i`  yolo
i  = unsafePerformIO $ newIORef 0

main::IO ()
main = do
            writeIORef i 2 -- nie ma i = 2, ale mamy setter :-)
            x1 <- readIORef i -- i getter :-)
            writeIORef i 3
            x2 <- readIORef i
            putStrLn $ show $ x1
            putStrLn $ show $ x2

To nawet jest jeszcze mało nieczysty fragment. A można pojechać dużo dalej, rzutować Stringi na Double itd. (unsafePerformIO jest w standardzie (Haskell2010)).

0
jarekr000000 napisał(a):

Pogrążamy haskell.

import Data.IORef
import System.IO.Unsafe (unsafePerformIO)
-- zmienna globalna  (!!!) `i`  yolo
i  = unsafePerformIO $ newIORef 0

main::IO ()
main = do
            writeIORef i 2 -- nie ma i = 2, ale mamy setter :-)
            x1 <- readIORef i -- i getter :-)
            writeIORef i 3
            x2 <- readIORef i
            putStrLn $ show $ x1
            putStrLn $ show $ x2

To nawet jest jeszcze mało nieczysty fragment. A można pojechać dużo dalej, rzutować Stringi na Double itd. (unsafePerformIO jest w standardzie (Haskell2010)).

Gdzie tu masz reassignment? Owszem, zmieniłeś wartość tej zmiennej, ale nie ma assignmentu.

zmienna globalna (!!!) i yolo

No i? Mogą być.

1

Gdzie tu masz reassignment? Owszem, zmieniłeś wartość tej zmiennej, ale nie ma assignmentu.

W takim razie mamy inną definicję assignmentu. Dalsza dyskusja nie ma sensu.

0
jarekr000000 napisał(a):

Gdzie tu masz reassignment? Owszem, zmieniłeś wartość tej zmiennej, ale nie ma assignmentu.

W takim razie mamy inną definicję assignmentu. Dalsza dyskusja nie ma sensu.

Wygodne.

Jak zadeklarujesz sobie stałą w dowolnym języku, to wszyscy się zgodzą że "stałej się nie zmienia w języku programowania". Ale mogę wieloma sposobami zmienić jej wartość, albo metaprogrammingiem, albo formą refleksji, albo edytując pamięć bezpośrednio w ramie. Wiadomo że nie można zrobić PRAWDZIWIE niemutowalnej wartości. Pytanie tylko na co pozwala język, a na co nie.

1

@TomRiddle: w Eliksirze możesz zrobić tak:

x = 1
x = 2

A mimo to nie masz mutowalności. Dowód?

x = 1
f = fn -> x end
x = 2

f.() # => 1

Masz tylko shadowing, "pierwsze x" to zupełnie inne x od "drugiego x". Po kompilacji powyższy przykład opowiada Erlangowemu zapisowi:

X@0 = 1
X@1 = 2

Czy w takim przypadku uważasz, że Elixir to język niefunkcyjny?

0
hauleth napisał(a):

@TomRiddle: w Eliksirze możesz zrobić tak:

x = 1
x = 2

A mimo to nie masz mutowalności. Dowód?

x = 1
f = fn -> x end
x = 2

f.() # => 1

Masz tylko shadowing, "pierwsze x" to zupełnie inne x od "drugiego x". Po kompilacji powyższy przykład opowiada Erlangowemu zapisowi:

X@0 = 1
X@1 = 2

Czy w takim przypadku uważasz, że Elixir to język niefunkcyjny?

Nie uważam tego za reassignment, tylko redefinition.

To co się dzieje, to zachowanie które opisałeś, jest dla mnie bardzo funkcyjne. Gdyby f() zwróciło 2, to byłby znak że eliksir jest nie funkcyjny, ale ponieważ zwraca 1 to wszystko jest git i dla mnie to jest funkcyjne.

1 użytkowników online, w tym zalogowanych: 0, gości: 1