your programing

Haskell (때때로)이 "Best Imperative Language"로 불리는 이유는 무엇입니까?

lovepro 2020. 10. 5. 20:34
반응형

Haskell (때때로)이 "Best Imperative Language"로 불리는 이유는 무엇입니까?


(이 질문이 주제와 관련이 있기를 바랍니다. 답변을 검색했지만 확실한 답변을 찾지 못했습니다. 주제에서 벗어 났거나 이미 답변 된 경우 검토 / 삭제하십시오.)

Haskell이 최고의 명령형 언어 라는 반 농담을 몇 번 듣고 읽었던 기억이 있습니다 . 물론 Haskell이 일반적으로 기능적 특징 으로 가장 잘 알려져 있기 때문에 이상하게 들립니다 .

그래서 내 질문은 Haskell의 어떤 특성 / 특징이 (있는 경우) Haskell이 최고의 명령형 언어 로 간주되는 이유를 제시하는 것 입니다. 아니면 실제로 농담에 가깝습니까?


나는 그것을 반 진실이라고 생각합니다. Haskell은 추상화하는 놀라운 능력을 가지고 있으며, 여기에는 명령형 아이디어에 대한 추상화가 포함됩니다. 예를 들어, Haskell에는 명령형 while 루프가 내장되어 있지 않지만 작성 만하면됩니다.

while :: (Monad m) => m Bool -> m () -> m ()
while cond action = do
    c <- cond
    if c 
        then action >> while cond action
        else return ()

이 수준의 추상화는 많은 명령형 언어에서 어렵습니다. 이것은 클로저가있는 명령형 언어로 수행 할 수 있습니다. 예. Python 및 C #.

그러나 Haskell 은 Monad 클래스를 사용하여 허용되는 부작용특성화 하는 (매우 고유 한) 기능도 가지고 있습니다. 예를 들어, 함수가있는 경우 :

foo :: (MonadWriter [String] m) => m Int

이것은 "명령 적"함수일 수 있지만 두 가지만 수행 할 수 있다는 것을 알고 있습니다.

  • 문자열 스트림 "출력"
  • Int를 반환

콘솔에 인쇄하거나 네트워크 연결 등을 설정할 수 없습니다. 추상화 기능과 결합하여 "스트림을 생성하는 모든 계산"등에 작용하는 함수를 작성할 수 있습니다.

매우 훌륭한 명령형 언어로 만드는 것은 Haskell의 추상화 능력에 관한 것입니다.

그러나 거짓 절반은 구문입니다. 나는 Haskell을 명령형 스타일로 사용하는 것이 매우 장황하고 어색하다고 생각합니다. 다음은 while연결 목록의 마지막 요소를 찾는 위의 루프를 사용한 명령형 계산의 예입니다 .

lastElt :: [a] -> IO a
lastElt [] = fail "Empty list!!"
lastElt xs = do
    lst <- newIORef xs
    ret <- newIORef (head xs)
    while (not . null <$> readIORef lst) $ do
        (x:xs) <- readIORef lst
        writeIORef lst xs
        writeIORef ret x
    readIORef ret

모든 IORef 가비지, 이중 읽기, 읽기 결과 바인딩, fmapping ( <$>) 인라인 계산 결과 작업 ... 모두보기가 매우 복잡합니다. 기능적 관점에서 보면 상당히 의미가 있지만 명령형 언어는 이러한 세부 사항의 대부분을 사용하기 쉽게 만들기 위해 깔개 아래로 훑어 보는 경향이 있습니다.

물론 다른 while스타일의 결합자를 사용하면 더 깨끗할 것입니다. 그러나 그 철학을 충분히 이해한다면 (자신을 명확하게 표현하기 위해 풍부한 조합자를 사용하여), 다시 함수형 프로그래밍에 도달하게됩니다. 명령형 하스켈은 잘 설계된 명령형 언어 (예 : 파이썬)처럼 "흐르지"않습니다.

결론적으로, 통사적인 면모를 가진 Haskell은 최고의 명령형 언어 일 수 있습니다. 그러나 페이스 리프트의 특성상 내부적으로 아름답고 실제적인 것을 외부 적으로 아름답고 가짜로 대체하는 것입니다.

편집 : lastElt이 파이썬 음역과 대조 :

def last_elt(xs):
    assert xs, "Empty list!!"
    lst = xs
    ret = xs.head
    while lst:
        ret = lst.head
        lst = lst.tail
    return ret 

동일한 수의 라인이지만 각 라인에는 노이즈가 상당히 적습니다.


2 편집

그만한 가치 는 Haskell 순수한 대체품 이 어떻게 생겼는지입니다.

lastElt = return . last

그게 다야. 또는 다음을 사용하는 것을 금지하는 경우 Prelude.last:

lastElt [] = fail "Unsafe lastElt called on empty list"
lastElt [x] = return x
lastElt (_:xs) = lastElt xs

또는 모든 Foldable데이터 구조 에서 작동하고 실제로 오류를 처리 할 필요 가 없다는 것을 인식하고 싶다면 IO:

import Data.Foldable (Foldable, foldMap)
import Data.Monoid (Monoid(..), Last(..))

lastElt :: (Foldable t) => t a -> Maybe a
lastElt = getLast . foldMap (Last . Just)

와 함께 Map, 예 :

λ➔ let example = fromList [(10, "spam"), (50, "eggs"), (20, "ham")] :: Map Int String
λ➔ lastElt example
Just "eggs"

(.)연산자는 함수 조성물 .


It's not a joke, and I believe it. I'll try to keep this accessible for those who don't know any Haskell. Haskell uses do-notation (among other things) to allow you to write imperative code (yes, it uses monads, but don't worry about that). Here's some of the advantages that Haskell gives you:

  • Easy creation of subroutines. Let's say that I want a function to print a value to stdout and stderr. I can write the following, defining the subroutine with one short line:

    do let printBoth s = putStrLn s >> hPutStrLn stderr s
       printBoth "Hello"
       -- Some other code
       printBoth "Goodbye"
    
  • Easy to pass code around. Given that I've written the above, if I now want to use the printBoth function to print out all of a list of strings, that's easily done by passing my subroutine to the mapM_ function:

    mapM_ printBoth ["Hello", "World!"]
    

    Another example, although not imperative, is sorting. Let's say you want to sort strings solely by length. You can write:

    sortBy (\a b -> compare (length a) (length b)) ["aaaa", "b", "cc"]
    

    Which will give you ["b", "cc", "aaaa"]. (You can write it shorter than that, too, but never mind for now.)

  • Easy to re-use code. That mapM_ function is used a lot, and replaces for-each loops in other languages. There's also forever which acts like a while (true), and various other functions that can be passed code and execute it in different ways. So loops in other languages are replaced by these control functions in Haskell (which are not special -- you can define them yourself very easily). In general this makes it hard to get the loop condition wrong, just like for-each loops are harder to get wrong than the long-hand iterator equivalents (e.g. in Java), or array-indexing loops (e.g. in C).

  • Binding not assignment. Basically, you can only assign to a variable once (rather like single static assignment). This removes a lot of confusion about the possible values of a variable at any given point (its value is only set on one line).
  • Contained side effects. Let's say that I want to read a line from stdin, and write it on stdout after applying some function to it (we'll call it foo). You can write:

    do line <- getLine
       putStrLn (foo line)
    

    I know immediately that foo doesn't have any unexpected side effects (like updating a global variable, or deallocating memory, or whatever), because it's type must be String -> String, which means it is a pure function; whatever value I pass, it must return the same result every time, without side effects. Haskell nicely separates the side-effecting code from the pure code. In something like C, or even Java, this is not obvious (does that getFoo() method change state? You'd hope not, but it might do...).

  • Garbage collection. A lot of languages are garbage collected these days, but worth mentioning: no hassles of allocating and deallocating memory.

There's probably a few more advantages besides, but those are the ones that come to mind.


In addition to what other's have already mentioned, having side-effecting actions be first-class is sometimes useful. Here's a silly example to show the idea:

f = sequence_ (reverse [print 1, print 2, print 3])

This example shows how you can build up computations with side-effects (in this example print) and then put the in data structures or manipulate them in other ways, before actually executing them.

참고URL : https://stackoverflow.com/questions/6622524/why-is-haskell-sometimes-referred-to-as-best-imperative-language

반응형