KomplexitÀt der Einfachheit


Wie ich im Vorwort des vorherigen Artikels geschrieben habe , bin ich auf der Suche nach einer Sprache, in der ich weniger schreiben und mehr Sicherheit haben kann. Meine Hauptprogrammiersprache war immer C #, daher habe ich mich entschlossen, zwei Sprachen auszuprobieren, die sich in Bezug auf die KomplexitĂ€t symmetrisch davon unterscheiden. Ich hatte bisher nur davon gehört, konnte aber nicht schreiben: Haskell und Go. Eine Sprache wurde dafĂŒr bekannt, "Erfolg um jeden Preis vermeiden" * zu sagen, wĂ€hrend die andere meiner bescheidenen Meinung nach das genaue Gegenteil ist. Am Ende wollte ich verstehen, was sich als besser herausstellen wĂŒrde: absichtliche Einfachheit oder absichtliche Strenge?


Ich habe mich entschlossen, eine Lösung fĂŒr ein Problem zu schreiben und zu sehen, wie einfach es in beiden Sprachen ist, wie die Lernkurve fĂŒr einen erfahrenen Entwickler ist, wie viel dafĂŒr gelernt werden sollte und wie idiomatisch der „Neuling“ -Code in dem einen und dem anderen Fall ist. Außerdem wollte ich verstehen, wie viel ich irgendwann bezahlen mĂŒsste, um den Haskell-Compiler zufrieden zu stellen, und wie viel Zeit die berĂŒhmte Bequemlichkeit von Goroutinen sparen wĂŒrde. Ich habe versucht, so unvoreingenommen wie möglich zu sein, und ich werde am Ende des Artikels eine subjektive Meinung abgeben. Die endgĂŒltigen Ergebnisse haben mich sehr ĂŒberrascht, und ich entschied, dass es fĂŒr die BĂŒrger von Chabrowsk interessant sein wĂŒrde, ĂŒber einen solchen Vergleich zu lesen.




Und sofort eine kleine Bemerkung. Tatsache ist, dass der Ausdruck (*) oft ironisch verwendet wird, aber nur, weil die Leute ihn falsch analysieren. Sie lesen es als "Vermeiden (Erfolg) (um jeden Preis)", dh "egal was passiert, wenn es zum Erfolg fĂŒhrt, mĂŒssen wir es vermeiden", wĂ€hrend der eigentliche Satz lautet "Vermeiden (Erfolg ĂŒberhaupt) Kosten) ", das heißt," wenn der Preis fĂŒr den Erfolg zu hoch ist, mĂŒssen wir zurĂŒcktreten und alles ĂŒberdenken. " Ich hoffe, dass es nach dieser ErklĂ€rung nicht mehr lĂ€cherlich ist und eine echte Bedeutung gefunden hat: Die Ideologie der Sprache erfordert die richtige Planung Ihrer Anwendung, und fĂŒgen Sie keine Ad-hoc-KrĂŒcken ein, wenn sie zu viel kosten. Die Ideologie von Go wiederum lautet eher: "Der Code sollte einfach genug sein, damit er bei sich Ă€ndernden Anforderungen leicht weggeworfen und neu geschrieben werden kann."


Vergleichstechnik


Ohne weiteres nahm ich das Puzzle, das der Genosse 0xd34df00d erfunden hatte, und es klingt so:


Angenommen, wir haben einen Baum von Bezeichnern einiger EntitÀten, z. B. Kommentare (im Speicher, in beliebiger Form). Es sieht so aus:


|- 1
   |- 2
   |- 3
      |- 4
      |- 5

API /api/{id} JSON- .


, , API, . , , IO .


API, , . , , , ..


:


|- 1
   |- 2
   |- 3
      |- 4
      |- 5

|- 1: 
   |- 2:   1
   |- 3:   2
      |- 4:   1
      |- 5:   2

https://jsonplaceholder.typicode.com/todos/


, , , .


Haskell



, -, - do-, . , . C#



- .


Disclaimer: , Haskell


, ML . , , Lingua Franca - * . , , , . . , , .


* /++/..., — C++/C#/Java/Kotlin/Swift/...


, Haskell. Scala/F#/Idris/Elm/Elixir/
 , — , . , , Haskell . Rust/C#, , . Option/Maybe, Result/Either Task/Promise/Future/IO .


,


data Maybe a = Just a | Nothing  --  


//   
enum Maybe<T> {
   Just(T),
   Nothing
}

, . Rust: - - T. - . . ,


data Either a b = Left a | Right b

,


enum Either<A,B> {
   Left(A),
   Right(B)
}

. , ( - ).


- -, , :


data Comment = Comment {
      title :: String
    , id  :: Int
    } deriving (Show) --     
              --    (  ToString()  C#/Java)

:


#[derive(Debug)]
struct Comment {
    title: String,
    id: i32
}

, .



sqr :: Int -> Int
sqr x = x*x

main :: IO () -- IO   ,     ,     
main = print (sqr 3)


fn sqr(x: i32) -> i32 { x*x }
fn main() {
   println!("{}", sqr(3));
}

, — , — main.


, : - , ML- — . - - . (sqr 3) , , . print sqr , sqr Fn(i32) -> i32 (Func<int, int> C#), show ( ToString()).


: : () — , — . - ( ) ->, . , foo :: Int -> Double -> String -> Bool, foo : , , .


, bar :: (Int -> Double) -> Int -> (String -> Bool)?


bar : Int -> Double Int, String -> bool.


Rust-: fn bar(f: impl Fn(i32) -> f64, v: i32) -> impl Fn(String) -> bool


C#-: Func<string, bool> Bar(Func<int, double> f, int v)


, ( ), . , , , .


,


sqr x = x*x     --     ,   
add x y = x + y -- : FOR EXAMPLE PURPOSES ONLY!
add5_long x = add 5 x
add5 = add 5    --    ,       , 
                --  add5     add5_long. 
                --    Method Groups  C#
                --     - 

main :: IO ()
main = putStrLn (show (add 10 (add5 (sqr 3)))) 


fn sqr(x: i32) -> i32 { x*x }
fn add(x: i32, y: i32) -> i32 { x + y }
fn add5(x: i32) -> i32 { add(5, x) }

fn main() {
   println!("{}", ToString::to_string(add(10, add(5, sqr(3)))));                
}

, . $ . a $ b a (b). :


main = putStrLn $ show $ add 10 $ add5 $ sqr 3 -- !  

, - . . — , f (g x) = (f . g) x. print (sqr 3) (print . sqr) 3. "" " " " ", 3. :


main = putStrLn . show . add 10 . add5 $ sqr 3

, ? , — , , - :


--     5,   10,    ,    
putStrLnShowAdd10Add5 = putStrLn . show . add 10 . add5
--   putStrLnShowAdd10Add5 x = putStrLn . show . add 10 . add5 x
--      (, ,    )

main :: IO ()
main = putStrLnShowAdd10Add5 $ sqr 3

"24". — , , — — .


ML , Haskell ,



main :: IO ()
main = 
  let maybe15 = do
      let just5 = Just 5    --    Maybe   Just (.  )   5
      let just10 = Just 10  --     10
      a <- just5            --     ,   ,     `a`.           !
      b <- just10           --     `b`
      return $ a + b        --      ,      (    Just)      a  b.
  in 
    print maybe15

, do- <- , do-, , ( , ). Maybe - ( "Null condition operator"), null- , null, - . do- - , .


, ? , "" , ( Maybe, , Result<T,Error>, — Either), , ? , <- , .


, async/await ( C# .. Rust async-await ):


async ValueTask<int> Maybe15() {
    var just5 = Task.FromResult(5);
    var just10 = Task.FromResult(10);
    int a = await just5; //         !
    int b = await just10;
    return a + b;
}

Console.WriteLine(Maybe15().ToString()) //   15

Task Maybe, , .
, do- async/await ( Task) , do — " async-", <- — "" ( ). Future/Option/Result/ List, .


C#

C# do-, async/await Task. , , LINQ-. , , - , . Nullable, Haskell ( Task). , ( ):


main :: IO ()
main = 
  let maybe15 = do
      a <- Just 5
      b <- Just 10
      return $ a + b 
  in 
    print maybe15

C#


int? maybe15 = from a in new int?(5)
               from b in new int?(10)
               select a + b;

Console.WriteLine(maybe15?.ToString() ?? "Nothing");

? — , haskell Nothing , C# .


repl ( ). Result Task. , . , (, , ?).


Haskell: https://repl.it/@Pzixel/ChiefLumpyDebugging


, ,


Haskell



? , IDE


: GHC (), Stack ( , cargo dotnet), IntelliJ-Haskell, . , IDE .


, , , , , :


main :: IO ()
main = putStrLn "Hello, World!"

, ! , . Data.Tree drawTree. , , :


import Data.Tree

main :: IO ()
main = do
    let tree = Node 1 [Node 2 [], Node 3 [Node 4 [], Node 5 []]]
    putStrLn . drawTree $ tree --      ,     .
                               --        

:


    ‱ No instance for (Num String) arising from the literal ‘1’
    ‱ In the first argument of ‘Node’, namely ‘1’
      In the expression:
        Node 1 [Node 2 [], Node 3 [Node 4 [], Node 5 []]]

- 30 , " ?.. 
 , ", "haskell map convert to string", map show. : putStrLn . drawTree . fmap show $ tree, ,


, , ?
, , . , , - - . .. - Rust , Option — , - Maybe ( Option), , HTTP , . , .


-:


import Data.Tree

data Comment = Comment {
      title :: String
    , id  :: Int
    } deriving (Show)

getCommentById :: Int -> Maybe Comment
getCommentById i = Just $ Comment (show i) i

main :: IO ()
main = do
    let tree = Node 1 [Node 2 [], Node 3 [Node 4 [], Node 5 []]]
    putStrLn . drawTree . fmap show $ tree

, - . "haskell map maybe list" ( , , ), " mapM". :


import Data.Tree
import Data.Maybe

data Comment = Comment {
      title :: String
    , id  :: Int
    } deriving (Show)

getCommentById :: Int -> Maybe Comment
getCommentById i = Just $ Comment (show i) i

main :: IO ()
main = do
    let tree = Node 1 [Node 2 [], Node 3 [Node 4 [], Node 5 []]]
    putStrLn . drawTree . fmap show $ tree
    let commentsTree = mapM getCommentById tree
    putStrLn . drawTree . fmap show $ fromJust commentsTree

:


1
|
+- 2
|
`- 3
   |
   +- 4
   |
   `- 5
Comment {title = "1", id = 1}
|
+- Comment {title = "2", id = 2}
|
`- Comment {title = "3", id = 3}
   |
   +- Comment {title = "4", id = 4}
   |
   `- Comment {title = "5", id = 5}

, . fromJust ( unwrap() Nullable.Value C#, , , ), , .


, , JSON'.
, wreq . 15 :


{-# LANGUAGE DeriveGeneric #-}

import Data.Tree
import Data.Maybe
import Network.Wreq
import GHC.Generics
import Data.Aeson
import Control.Lens

data Comment = Comment {
      title :: String
    , id  :: Int
    } deriving (Generic, Show)

instance FromJSON Comment -- `impl FromJson for Comment {}`   Rust

getCommentById :: Int -> IO Comment
getCommentById i = do
  response <- get $ "https://jsonplaceholder.typicode.com/todos/" ++ show i
  let comment = decode (response ^. responseBody) :: Maybe Comment
  return $ fromJust comment

main :: IO ()
main = do
    let tree = Node 1 [Node 2 [], Node 3 [Node 4 [], Node 5 []]]
    Prelude.putStrLn . drawTree . fmap show $ tree
    let commentsTree = mapM getCommentById tree
    Prelude.putStrLn . drawTree . fmap show $ fromJust commentsTree


 20 , (, reqwest Rust), :


    * Couldn't match expected type `Maybe (Tree a0)'
                  with actual type `IO (Tree Comment)'
    * In the first argument of `fromJust', namely `commentsTree'
      In the second argument of `($)', namely `fromJust commentsTree'
      In a stmt of a 'do' block:
        putStrLn . drawTree . fmap show $ fromJust commentsTree
   |
28 |     Prelude.putStrLn . drawTree . fmap show $ fromJust commentsTree
   |                                                        ^^^^^^^^^^^^^

, fromJust Maybe Tree -> Tree, IO , IO Tree Maybe Tree. ? , " <-" . :


main :: IO ()
main = do
    let tree = Node 1 [Node 2 [], Node 3 [Node 4 [], Node 5 []]]
    Prelude.putStrLn . drawTree . fmap show $ tree
    commentsTree <- mapM getCommentById tree
    Prelude.putStrLn . drawTree . fmap show $ commentsTree

, . . , .


20 , . Concurrent-, - , -. , async. , - :


commentsTree <- mapConcurrently getCommentById tree

. , , . , — .


: .. , HTTP , repl.it .


. C# Rust . 67 -. , reqwest 100 , . , .


. , , . , :


main = do
    let tree = [1,2,3,4,5]
    print tree
    commentsTree <- mapConcurrently getCommentById tree
    print commentsTree


[1,2,3,4,5]
[Comment {title = "delectus aut autem", id = 1},Comment {title = "quis ut nam facilis et officia qui", id = 2},Comment {title = "fugiat veniam minus", id = 3},Comment {title = "et porro tempora", id = 4},Comment {title = "laboriosam mollitia et enim quasi adipisci quia provident illum", id = 5}]

, . , , . , , ( , ), , , , . , , .


, , , go. , ( , go ) . , (, ), — !


Go



,


, , https://play.golang.org/ .


, . , . go :


package main

type intTree struct {
    id int
    children []intTree
}

func main() {
    tree := intTree{ //    gofmt 
        id: 1,
        children: []intTree {
            {
                id: 2,
                children: []intTree{

                },
            },
            {
                id: 3,
                children: []intTree{
                    {
                        id: 4,
                    },
                    {
                        id: 5,
                    },
                },
            },
        },
    }
}

— , tree declared and not used. , , , tree _. , no new variables on left side of :=. , , . , , foreach :


func showIntTree(tree intTree) {
    showIntTreeInternal(tree, "")
}

func showIntTreeInternal(tree intTree, indent string) {
    fmt.Printf("%v%v\n", indent, tree.id)
    for _, child := range tree.children {
        showIntTreeInternal(child, indent + "  ")
    }
}

, , , . Haskell , .


, . ,


type comment struct {
    id int
    title string
}

type commentTree struct {
    value comment
    children []commentTree
}

func loadComments(node intTree) commentTree {
    result := commentTree{}
    for _, c := range node.children {
        result.children = append(result.children, loadComments(c))
    }
    result.value = getCommentById(node.id)
    return result
}

func getCommentById(id int) comment { 
    return comment{id:id, title:"Hello"} //     go
}

:


func showCommentsTree(tree commentTree) {
    showCommentsTreeInternal(tree, "")
}

func showCommentsTreeInternal(tree commentTree, indent string) {
    fmt.Printf("%v%v - %v\n", indent, tree.value.id, tree.value.title)
    for _, child := range tree.children {
        showCommentsTreeInternal(child, indent + "  ")
    }
}

, -, . , http , , JSON, 5 :


func getCommentById(i int) comment {
    result := &comment{}
    err := getJson("https://jsonplaceholder.typicode.com/todos/"+strconv.Itoa(i), result)
    if err != nil {
        panic(err) //    
    }
    return *result
}

func getJson(url string, target interface{}) error {
    var myClient = &http.Client{Timeout: 10 * time.Second}
    r, err := myClient.Get(url)
    if err != nil {
        return err
    }
    defer r.Body.Close()

    return json.NewDecoder(r.Body).Decode(target)
}

,


1
  2
  3
    4
    5
0 - 
  0 - 
  0 - 
    0 - 
    0 - 

. , , . .


5 , , : {Title = "delectus aut autem", Id = 1} c id, title . , , — . , : — , — , .


DTO, , , .


10 , ! — . , , , , , .


5, go-.


func loadComments(root intTree) commentTree {
    var wg sync.WaitGroup
    result := loadCommentsInner(&wg, root)
    wg.Wait()
    return result
}

func loadCommentsInner(wg *sync.WaitGroup, node intTree) commentTree {
    result := commentTree{}
    wg.Add(1)
    for _, c := range node.children {
        result.children = append(result.children, loadCommentsInner(wg, c))
    }
    go func() {
        result.value = getCommentById(node.id)
        wg.Done()
    }()
    return result
}


0 - 
  0 - 
  0 - 
    0 - 
    0 - 

, - ? , , . , , wg.Wait() , , .


, - , . , , , 10 :


func loadComments(root intTree) commentTree {
    ch := make(chan commentTree, 1)   //  
    var wg sync.WaitGroup             //   
    wg.Add(1)                         //     
    loadCommentsInner(&wg, ch, root)  //  
    wg.Wait()                         //  
    result := <- ch                   //    
    return result
}

func loadCommentsInner(wg *sync.WaitGroup, channel chan commentTree, node intTree) {
    ch := make(chan commentTree, len(node.children))  //     
    var childWg sync.WaitGroup                        //     
    childWg.Add(len(node.children))
    for _, c := range node.children {
        go loadCommentsInner(&childWg, ch, c)         //      ()
    }
    result := commentTree{
        value: getCommentById(node.id),               //   
    }
    if len(node.children) > 0 {                       //     ,   ,   
        childWg.Wait()
        for value := range ch {                       //      ,    
            result.children = append(result.children, value)
        }
    }
    channel <- result                                 //     
    wg.Done()                                         //     
}


 . . , . , , , . - . ?


15 , , / , 
 HTTP :


func getCommentById(id int) comment {
    return comment{Id: id, Title: "Hello"}
}

:


1
  2
  3
    4
    5
fatal error: all Goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc00006e228)
    C:/go/src/runtime/sema.go:56 +0x49
sync.(*WaitGroup).Wait(0xc00006e220)
    C:/go/src/sync/waitgroup.go:130 +0x6b
main.loadCommentsInner(0xc00006e210, 0xc0000ce120, 0x1, 0xc000095f10, 0x2, 0x2)
    C:/Users/pzixe/go/src/hello/hello.go:47 +0x187
main.loadComments(0x1, 0xc000095f10, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
    C:/Users/pzixe/go/src/hello/hello.go:30 +0xec
main.main()
    C:/Users/pzixe/go/src/hello/hello.go:94 +0x14d

, - . , - ? HTTP , ...


@gogolang , . , :


  1. . , , comment JSON
  2. . , Task Task WhenAny/WhenAll . , , , . , , . — - -
    for httpRequest := range webserer.listen() {
        go handle(httpRequest)
    } 
  3. go-way printTree:


    func printTree(tree interface{}) string {
        b, err := json.MarshalIndent(tree, "", "  ")
        if err != nil {
            panic(err)
        }
        return string(b)
    }

    interface {} — dynamic any, . , .


    JSON , .



, : , , ? . :


func loadComments(root intTree) commentTree {
    result := commentTree{}
    var wg sync.WaitGroup
    loadCommentsInner(&result, root, &wg)
    wg.Wait()
    return result
}

func loadCommentsInner(resNode *commentTree, node intTree, wg *sync.WaitGroup) {
    wg.Add(1)
    for _, res := range node.children {
        resNode.children = append(resNode.children, &commentTree{})
        loadCommentsInner(resNode.children[len(resNode.children)-1], res, wg)
    }
    resNode.value = getCommentById(node.id)
    wg.Done()
}

? , "go-way": , . , , , .


, . , , ,


, , .


, , - :


func loadComments(root intTree) commentTree {
    result := commentTree{}
    var wg sync.WaitGroup
    loadCommentsInner(&result, root, &wg)
    wg.Wait()
    return result
}

func loadCommentsInner(resNode *commentTree, node intTree, wg *sync.WaitGroup) {
    wg.Add(len(node.children))
    for _, res := range node.children {
        child := &commentTree{}
        resNode.children = append(resNode.children, child)
        res := res
        go func() {
            defer wg.Done()
            loadCommentsInner(child, res, wg)
        }()
    }
    resNode.value = getCommentById(node.id)
}

, , . , , . , (, 4 , ), , .


, , . :


1
  2
  3
    4
    5
START 1
START 3
START 5
DONE 5
START 2
DONE 2
START 4
DONE 4

, 4 5 , 3 . , , , . , Rust , , , .


, , , :


if len(node.children) > 0 {
    childWg.Wait()
    for i := 0; i < len(node.children); i++ { //      
        result.children = append(result.children, <-ch)
    }
}
channel <- result
wg.Done()

, ? , , , , . , :


  1. func (n *IdTree) LoadComments(ctx context.Context) (*CommentTree, error) — tree
  2. g, ctx := errgroup.WithContext(ctx) —
  3. i, c := i, c // correct capturing by closure — , , .
  4. g.Go(func() error — ,

, , .


, ? , . ( ), , (- ), . , , .


, ? , , , . getCommentById.


.


C♯


C# , 6 , . , , , 8 :


class Program
{
    class Comment
    {
        public int Id { get; set; }
        public string Title { get; set; }

        public override string ToString() => $"{Id} - {Title}";
    }

    private static readonly HttpClient HttpClient = new HttpClient();

    private static Task<Comment> GetCommentById(int id) =>
        HttpClient.GetStringAsync($"https://jsonplaceholder.typicode.com/todos/{id}")
            .ContinueWith(n => JsonConvert.DeserializeObject<Comment>(n.Result));

    private static async Task<Tree<Comment>> GetCommentsTree(Tree<int> tree)
    {
        var children = Task.WhenAll(tree.Children.Select(GetCommentsTree));
        var value = await GetCommentById(tree.Value);
        var childrenResults = await children;
        return new Tree<Comment> { Value = value, Children = childrenResults };
    }

    private static async Task Main()
    {
        var tree = Tr(1, new[] { Tr(2), Tr(3, new[] { Tr(4), Tr(5) }) });
        PrintTree(tree);
        var comment_tree = await GetCommentsTree(tree);
        PrintTree(comment_tree);
    }

    class Tree<T>
    {
        public T Value { get; set; }
        public Tree<T>[] Children { get; set; }
    }

    private static void PrintTree<T>(Tree<T> tree, string intendantion = "")
    {
        Console.WriteLine(intendantion + tree.Value);
        foreach (var child in tree.Children)
        {
            PrintTree(child, intendantion + "  ");
        }
    }

    static Tree<T> Tr<T>(T value, Tree<T>[] children = null) => new Tree<T> { Value = value, Children = children ?? Array.Empty<Tree<T>>() };
}

, ? , , Task.WhenAll foreach . .
? , . GetCommentById. , AsyncEnumerable , , .




, . , , , , . . , .


HaskellgoC#
*177628
24-
3010-
1550-
✓✗✗
✓✗✓
**✓✗✓✓
~3011
5c1.9c***1.6c

* , . , , Haskell showTree


** , 42. , , :


async Task<Tree<Comment>> GetCommentsTree(Tree<int> tree)
{
    if (tree.Children.Sum(c => c.Value) == 42)
    {
        return new Tree<Comment> { Value = await GetCommentById(tree.Value), Children = Array.Empty<Tree<int>> };
    }
    ...         

.


, , mapParallel. , , go C#.


*** , 7 , 1.9 , 200. , .




, :


""

Haskell: , . , , , , . , , 17 , , .


Go: - , - , , .


Haskell: , , . , " ", " Rust", . , . , . : . , , .


Go: . 10 . . , , . , , , , — . , . - , , , . , , , . , . , , .


C

. , , , , , Maybe IO , .


C

, - , . Task.Run(() => DoStuffAsyncInAnotherThread()) , - . , go DoAsyncStuff(), , ( - ), Task.WhenAny/Task.WhenAll ( ), .


. , , . . , , .


- , , , .



, .


, . : , , , . , , , , " ?", Maybe IO " fromJust Maybe, IO?".


, .. , . , , , - . . , " ", . , . , , . , , .




, , , - , . ? . JSON? . . , (, ^.), , (- HTTP ), .


, - , . , , — . . : , .




, .


IDE//etc, / .


.


- , - , — . , , //
 , ...


- , go 3 , . - , 8 , 5, - . , , , . .


- , ( ) . :


— go , ,

— *sarcasm*

— Sorry, this group is no longer accessible

, ( ), , .




, ? , - . , - , , , , 
 , .


Upd. , , . , . , .



? , . . , , obj/bin . , . , Go , , - . , , . - gorm, EF/Linq2Db/Hibernate/Dapper API.


, , , . "" , , , , . SQL , . , . , . , , , , "" , - . , , , ( , ). go Map, : , — .


, :



— , ?..

Source: https://habr.com/ru/post/de469441/


All Articles