Dość często jak mówię o Elixir czy też piszę pojawia się pytanie: a dlaczego nie F#? Dlaczego nie lisp? Clojure? Haskell? Odpowiedź jest dość prosta, ale może ona nie być zrozumiała dla niektórych. Elixir jest po prostu czytelny, beznadziejnie prosty i przejrzysty.

Poniżej, ta sama funkcja napisana w kilku językach funkcyjnych jak i obiektowych, teraz, proszę powiedzcie mi, która forma jest czytelna i jak w tym wszystkim plasuje się elixir?

;; lisp
(defun fibonacci (n &optional (a 0) (b 1) (acc ()))
  (if (zerop n)
      (nreverse acc)
      (fibonacci (1- n) b (+ a b) (cons a acc))))

;; clojure
(def fib-seq 
  ((fn rfib [a b] 
     (lazy-seq (cons a (rfib b (+ a b)))))
   0 1))

-- haskell
fib :: Integer -> Integer
fib 0 = 1
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

% erlang
-module(fib).    
-export([fib/1]).

fib(1) -> 1; 
fib(2) -> 1; 
fib(N) -> fib(N - 2) + fib(N - 1).

// f#
let rec fib n =
    match n with
    | 1 | 2 -> 1
    | n -> fib(n-1) + fib(n-2)

(* OCaml *)
let rec fib = function
  | 0 -> 0
  | 1 -> 1
  | n -> fib (n-1) + fib (n-2)

// Java
public static long fib(int n) {
    if (n <= 1) return n;
    else return fib(n-1) + fib(n-2);
}

// C#
public int Fib(int n)
{
    if (n == 0 || n == 1)
    {
        return n;
    }
    else 
    {
        return Fib(n - 1) + Fib(n - 2);
    }
}

A teraz to samo w elixir:

# elixir
defmodule Fib do 
  def fib(0) do 0 end
  def fib(1) do 1 end
  def fib(n) do fib(n-1) + fib(n-2) end
end

Jak widać nie różni się to zbytnio od zapisu C#. Do tego jest w tym wypadku przejrzysty jak haskell czy też erlang. Jednak te dwa języki szybko potrafią się stać mało czytelne. Czyli wiedziałem, że raczej z poziomu samego języka nie będe miał problemu zarówno zrozumieniem co jest napisane jak i w ogóle nauczeniem się jego posługiwaniem.

9 KOMENTARZE

  1. Rozumiem, że Fibonacci w Elixirze wygląda ładnie, aczkolwiek przyczepiłbym się do Clojure :-) Najprostsze Fibonacci bez TCO, lazy streamów i innych cudów wyglądałoby następująco https://repl.it/IXHx (link do repla). Tak więc jedynym udziwnieniem jest nienaturalna dla większości programistów notacja lispowa.
    Proszę o potraktowanie mojej wiadomości jako ciekawostki, nie chcę wywoływać świętej wojny. Niech każdy programuje w czym lubi i wybiera język odpowiedni do potrzeb :-)

    • @Paweł – i tylko te 5 ( albo i 6) nawiasów na końcu ;-)

      IMHO notacje F# i OCaml tak bardzo nie odbiegają od eliksirowej. Pomijam oczywiście – moim zdaniem przeklętą – wrażliwość na wcięcia… Coś okropnego…

      A z innej beczki – na ile to wszystko rozumiem – to te implementacje nie są tożsame? Zdaje się cześć obsługuje element zero a część nie…

      • Oj tak, “))))))))))))))))))))))))” i wysoki próg wejścia sprawiły, że zniechęciłem się do nauki Clojure.

  2. Cześć

    ciąg Fibonacciego można napisać pozbywając się podwójnego wywołania. Czytelnie i szybko. C#

    public long Fibonacci(long parameterA, long parameterB, long n)
    {
    if (n == 1)
    return parameterB;
    return Fibonacci(parameterB, parameterA + parameterB, n – 1);
    }

    Tu cały kod porównujący ilość wywołań i czasy obu metod(mam nadzieję, że działa)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Diagnostics;

    namespace FibTest
    {
    public class Program
    {
    public static long callBadFibonacciCount;
    public static long callGoodFibonacciCount;

    public static void Main(string[] args)
    {
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    long goodResult = FibonacciGood(0, 1, 10);
    stopwatch.Stop();
    Console.WriteLine(“good result: {0}, calls: {1}, time: {2}”, goodResult, callGoodFibonacciCount, stopwatch.Elapsed);
    stopwatch.Restart();
    long badResult = FibonacciBad(10);
    stopwatch.Stop();
    Console.WriteLine(“bad result: {0}, calls: {1}, time: {2}”, badResult, callBadFibonacciCount, stopwatch.Elapsed);
    }

    public static long FibonacciGood(long parameterA, long parameterB, long n)
    {
    callGoodFibonacciCount++;
    if (n == 1)
    return parameterB;
    return FibonacciGood(parameterB, parameterA + parameterB, n – 1);
    }

    public static long FibonacciBad(long n)
    {
    callBadFibonacciCount++;
    if (n == 1)
    return 1;
    if (n == 0)
    return 0;
    return FibonacciBad(n – 1) + FibonacciBad(n – 2);
    }
    }
    }

    Pozdrawiam serdecznie

    • czesc, dzieki. tak, mozna tez tak zrobic, co w jezykach funkcyjnych spowodowaloby tail optimization i przy duzych liczbach fib dzialaloby super fajnie. Jednak tutaj warto to zaznaczyc, w kazdym z jezykow ktore wymenilem da sie to zrobic w inny sposob do tego stopnia, ze w niektorych da sie cachowac poprzedni wyniki wiec nigdy nie liczy sie juz raz wykonanego dzialania.

      wiec jak najbardziej da sie tak zapisać i będzie on dużo mniej rzeczy na stos odkładał :) jednak jakbsmy juz tak bardzo chcieli w C# by to bylo wydajne i szybkie, to while/for bylby tutaj najlepszy.

  3. […] Jednym z pierwszych problemów jak i największą zaletą Elixir jest sama semantyka języka. Jest ona prosta, banalna, ograniczona do kilku makr. Opanowanie językowej składni to maks kilka minut nauki. Opanowanie możliwości czytania kodu ze zrozumieniem – około dwóch tygodni maks. Język nie wygląda jak język funkcyjny. Wręcz przeciwnie, wygląda jak język obiektowy. O czym ostatnio pisałem. […]

Comments are closed.