简单的复杂性


正如我在上一篇文章的序言中所写,我正在寻找一种语言,可以减少编写代码并提高安全性。 我的主要编程语言一直都是C#,所以我决定尝试两种在复杂性方面与它对称不同的语言,这是我之前才听说过的,但我却不能写:Haskell和Go。 一种语言因说“不惜一切代价避免成功”而闻名,而另一种语言则完全相反。 最后,我想了解什么会变得更好:有意简单或有意严谨?


我决定为一个问题写一个解决方案,然后看看这两种语言有多么容易,对于有经验的开发人员来说,他们的学习曲线是什么,应该研究多少,以及在另一种情况下“新手”代码的惯用性。 另外,我想了解,最终要满足Haskell编译器需要付出多少费用,以及著名的goroutine便利会节省多少时间。 我试图尽可能保持公正,并在本文结尾给出主观意见。 最终结果使我非常惊讶,因此我认为哈勃罗夫斯克市民阅读这种比较将很有趣。




并立即发表一点评论。 事实是,表达式(*)经常被讽刺地使用,但这仅仅是因为人们错误地解析了它。 他们将其读为“避免(不惜一切代价)(成功)”,也就是说,“无论发生什么情况,如果成功都会导致成功,我们就必须避免它”,而真正的短语则是“避免(不惜一切代价)”。成本”,即“如果成功的代价太高,那么我们必须退后一步,重新思考一切。” 我希望经过这种解释后,它不再是荒唐可笑的,而是找到了真正的意义:语言的意识形态要求您对应用程序进行适当的规划,并且不要在成本高昂的地方插入即兴拐杖。 反过来,Go的意识形态是“代码应该足够简单,以便在需求发生变化的情况下,很容易将其丢弃并编写新的代码。”


比较技术


事不宜迟 ,我接受了0xd34df00d同志发明的难题,听起来像这样:


假设我们有一棵某些实体的标识符树,例如注释(在内存中以任何形式)。 看起来像这样:


|- 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/zh-CN469441/


All Articles