Programing

Haskell에서 ()는 정확히 무엇입니까?

lottogame 2020. 11. 26. 07:44
반응형

Haskell에서 ()는 정확히 무엇입니까?


저는 Learn You a Haskell을 읽고 있으며, 모나드 장에서 ()모든 유형에 대해 일종의 "null"로 취급되는 것 같습니다 . ()GHCi 에서 유형을 확인 하면

>> :t ()
() :: ()

이것은 매우 혼란스러운 진술입니다. 그것은 ()그 자체로 모든 유형 인 것 같습니다 . 나는 그것이 언어에 어떻게 맞는지, 그리고 그것이 어떤 유형이든 어떻게 견딜 수 있는지에 대해 혼란 스럽습니다.


tl; dr () 은 모든 유형에 "null"값을 추가하지 않습니다. ()자체 유형의 "무딘"값 : ().

잠시 질문에서 뒤로 물러나 일반적인 혼란의 원인에 대해 설명하겠습니다. Haskell을 배울 때 흡수해야 할 핵심 사항은 표현 언어와 유형 언어의 차이입니다. 두 가지가 별도로 유지된다는 것을 알고있을 것입니다. 그러나 그것은 동일한 기호가 두 가지 모두에서 사용될 수 있도록 허용합니다. 그리고 그것이 여기서 진행되는 것입니다. 보고있는 언어를 알려주는 간단한 텍스트 단서가 있습니다. 이러한 신호를 감지하기 위해 전체 언어를 구문 분석 할 필요는 없습니다.

Haskell 모듈의 최상위 수준은 기본적으로 표현 언어로 존재합니다. 식 사이에 방정식을 작성하여 함수를 정의합니다. 그러나 표현식 언어에서 foo :: bar 를 보면 foo 가 표현식이고 bar 가 그 유형 임을 의미합니다 . 따라서을 읽을 때 표현식 언어에서와 유형 언어를 연결 () :: ()하는 문이 표시 됩니다. 기호는 같은 언어가 아니기 때문에 다른 것을 의미합니다. 이러한 반복은 표현 / 유형 언어 분리가 잠재 의식에 설치되어 도움이되는 니모닉이 될 때까지 초보자에게 종종 혼란을 야기합니다.()()()

키워드 data는 먼저 새로운 유형이 무엇인지, 두 번째로 그 값이 무엇인지를 말하므로 표현식과 유형 언어의 신중한 혼합을 포함하는 새로운 데이터 유형 선언을 도입합니다.

데이터 TyCon tyvar ... tyvar = ValCon1 유형 ... 유형 | ... | ValConn 유형 ... 유형

이러한 선언에서 유형 생성자 TyCon 이 유형 언어 에 추가되고 ValCon 값 생성자가 표현식 언어 (및 해당 패턴 하위 언어)에 추가됩니다. A의 data선언의에 대한 인수 장소에 서 일 ValCon의 것을 할 때의 당신에게 인수로 주어진 유형을 말할 ValCon는 표현에 사용됩니다. 예를 들면

data Tree a = Leaf | Node (Tree a) a (Tree a)

Tree노드에 요소를 저장하는 이진 트리 유형에 대한 유형 생성자 선언합니다 . 해당 값은 값 생성자 LeafNode. 저는 타입 생성자 (트리)를 파란색으로, 값 생성자 (리프, 노드)를 빨간색으로 칠하는 것을 좋아합니다. 표현식에 파란색이 없어야하며 (고급 기능을 사용하지 않는 한) 유형에 빨간색이 없어야합니다. 내장 유형 Bool을 선언 할 수 있습니다.

data Bool = True | False

파란색 추가 Bool유형의 언어, 빨간색 TrueFalse표현 언어에. 슬프게도 내 마크 다운-푸는이 게시물에 색상을 추가하는 작업에 부적절하므로 머리에 색상을 추가하는 방법을 배워야합니다.

"단위"유형은 ()특수 기호로 사용되지만 선언 된 것처럼 작동합니다.

data () = ()  -- the left () is blue; the right () is red

개념적으로 파란색 ()은 유형 언어의 유형 생성자이지만 개념적으로 빨간색 ()은 표현식 언어의 값 생성자이며 실제로 () :: (). [그런 말장난의 유일한 예는 아닙니다. 더 큰 튜플의 유형은 동일한 패턴을 따릅니다. 쌍 구문은 다음과 같이 지정됩니다.

data (a, b) = (a, b)

유형 및 표현 언어 모두에 (,) 추가. 그러나 나는 빗나 갔다.

따라서 ()종종 "Unit"으로 발음되는 유형은 말할 가치가있는 하나의 값을 포함하는 유형입니다. 해당 값은 작성 ()되었지만 표현식 언어로되어 있으며 때때로 "void"로 발음됩니다. 값이 하나 뿐인 유형은 그다지 흥미롭지 않습니다. 유형의 값은 ()정보의 0 비트를 제공합니다. 당신은 그것이 무엇이어야하는지 이미 알고 있습니다. 따라서 ()부작용을 나타내는 유형에는 특별한 것이 없지만 종종 모나드 유형의 값 구성 요소로 표시됩니다. 모나 딕 연산은 다음과 같은 유형을 갖는 경향이 있습니다.

val-in-type-1- > ...-> val-in-type-n- > effect-monad val-out-type

반환 유형은 유형 응용 프로그램입니다. 함수는 가능한 효과를 알려주고 인수는 작업에 의해 생성되는 값의 종류를 알려줍니다. 예를 들면

put :: s -> State s ()

(애플리케이션이 왼쪽에 연결되기 때문에 [ "우리 모두가 60 년대에했던 것처럼", Roger Hindley])

put :: s -> (State s) ()

하나의 값 입력 유형 s, effect-monad State s및 값 출력 유형이 ()있습니다. ()값 출력 유형으로 볼 때 이는 "이 작업은 효과를 위해서만 사용되며 전달 된 값은 흥미롭지 않습니다"를 의미합니다. 비슷하게

putStr :: String -> IO ()

문자열을 전달 stdout하지만 흥미로운 것은 반환하지 않습니다.

()유형은 컨테이너와 유사한 구조의 요소 유형으로도 유용합니다. 여기서 데이터 가 흥미로운 페이로드없이 shape 로만 구성됨을 나타냅니다 . 예를 들어, Tree위와 같이 선언 된 경우 Tree ()이진 트리 모양의 유형이며 노드에 관심있는 항목이 없습니다. 마찬가지로 [()]무딘 요소의 목록 유형도 마찬가지 입니다. 목록의 요소에 관심이없는 경우 목록이 제공하는 유일한 정보는 길이입니다.

요약 ()하면 유형입니다. 하나의 값인 ()은 동일한 이름을 갖지만 유형 및 표현 언어가 분리되어 있으므로 괜찮습니다. 컨텍스트 (예 : 모나드 또는 컨테이너)에서 컨텍스트 만 흥미 롭다는 것을 알려주기 때문에 "정보 없음"을 나타내는 유형을 갖는 것이 유용합니다.


()유형은 제로 요소 튜플로 생각 될 수있다. 하나의 값만 가질 수있는 유형이므로 유형이 필요한 곳에 사용되지만 실제로 정보를 전달할 필요는 없습니다. 여기에 몇 가지 용도가 있습니다.

단항 같은 것들 IOState리턴 값뿐만 아니라 수행 부작용이있다. 때때로 작업의 유일한 요점은 화면에 쓰기 또는 일부 상태 저장과 같은 부작용을 수행하는 것입니다. 화면에 쓰기를 들어, putStrLn유형이 있어야합니다 String -> IO ?- IO항상 어떤 반환 형식을 가지고있다, 그러나 여기에서 반환하는 유용한 아무것도 없다. 그렇다면 어떤 유형을 반환해야합니까? 우리는 Int라고 말하고 항상 0을 반환 할 수 있지만 오해의 소지가 있습니다. 따라서 우리 ()는 유용한 정보가 없음을 나타 내기 위해 하나의 값만있는 (따라서 유용한 정보가없는) 유형을 반환 합니다.

유용한 값을 가질 수없는 유형을 갖는 것이 때때로 유용합니다. 유형의 Map k v키를 유형의 k값에 매핑 하는 유형 구현했는지 고려하세요 v. 그런 다음 Set값 부분이 필요하지 않고 키만 필요하다는 점을 제외하면 맵과 정말 유사한 을 구현하려고 합니다. Java와 같은 언어에서는 더미 값 유형으로 부울을 사용할 수 있지만 실제로는 유용한 값이없는 유형을 원합니다. 그래서 당신은 말할 수 있습니다type Set k = Map k ()

()특히 마술이 아니라는 점에 유의해야합니다 . 원하는 경우 변수에 저장하고 패턴 일치를 수행 할 수 있습니다 (별로 중요하지는 않지만).

main = do
  x <- putStrLn "Hello"
  case x of
    () -> putStrLn "The only value..."

It is called the Unit type, usually used to represent side effects. You can think of it vaguely as Void in Java. Read more here and here etc. What can be confusing is that () syntactically represents both the type and its only value literal. Also note that it is not similar to null in Java which means an undefined reference - () is just effectively a 0-sized tuple.


I really like to think of () by analogy with tuples.

(Int, Char) is the type of all pairs of an Int and a Char, so it's values are all possible values of Int crossed with all possible values of Char. (Int, Char, String) is similarly the type of all triples of an Int, a Char, and a String.

It's easy to see how to keep extending this pattern upwards, but what about downwards?

(Int) would be the "1-tuple" type, consisting of all possible values of Int. But that would be parsed by Haskell as just putting parentheses around Int, and thus being just the type Int. And values in this type would be (1), (2), (3), etc, which also would just get parsed as ordinary Int values in parentheses. But if you think about it, a "1-tuple" is exactly the same as just a single value, so there's no need to actually have them exist.

Going down one step further to zero-tuples gives us (), which should be all possible combinations of values in an empty list of types. Well, there's exactly one way to do that, which is to contain no other values, so there should be only one value in the type (). And by analogy with tuple value syntax, we can write that value as (), which certainly looks like a tuple containing no values.

That's exactly how it works. There is no magic, and this type () and its value () are in no way treated specially by the language.

() is not in fact being treated as "a null value for any type" in the monads examples in the LYAH book. Whenever the type () is used the only value which can be returned is (). So it's used as a type to explicitly say that there cannot be any other return value. And likewise where another type is supposed to be returned, you cannot return ().

The thing to keep in mind is that when a bunch of monadic computations are composed together with do blocks or operators like >>=, >>, etc, they'll be building a value of type m a for some monad m. That choice of m has to stay the same throughout the component parts (there's no way to compose a Maybe Int with an IO Int in that way), but the a can and very often is different at each stage.

So when someone sticks an IO () in the middle of an IO String computation, that's not using the () as a null in the String type, it's simply using an IO () on the way to building an IO String, the same way you could use an Int on the way to building a String.


The confusion comes from other programming languages: "void" means in most imperative languages that there is no structure in memory storing a value. It seems inconsistent because "boolean" has 2 values instead of 2 bits, while "void" has no bits instead of no values, but there it is about what a function returns in a practical sense. To be exact: its single value consumes no bit of storage.

Let's ignore the value bottom (written _|_) for a moment...

() is called Unit, written like a null-tuple. It has only one value. And it is not called Void, because Void has not even any value, thus could not be returned by any function.


Observe this: Bool has 2 values (True and False), () has one value (()), and Void has no value (it doesn't exist). They are like sets with two/one/no elements. The least memory they need to store their value is 1 bit / no bit / impossible, respectively. Which means that a function that returns a () may return with a result value (the obvious one) that may be useless to you. Void on the other hand would imply that that function will never return and never give you any result, because there would not exist any result.

If you want to give "that value" a name, that a function returns which never returns (yes, this sounds like crazytalk), then call it bottom ("_|_", written like a reversed T). It could represent an exception or infinity loop or deadlock or "just wait longer". (Some functions will only then return bottom, iff one of their parameters is bottom.)

When you create the cartesian product / a tuple of these types, you will observe the same behaviour: (Bool,Bool,Bool,(),()) has 2·2·2·1·1=6 differnt values. (Bool,Bool,Bool,(),Void) is like the set {t,f}×{t,f}×{t,f}×{u}×{} which has 2·2·2·1·0=0 elements, unless you count _|_ as a value.


Yet another angle:

() is the name of a set which contains a single element called ().

Its indeed slightly confusing that the name of the set and the element in it happens to be the same in this case.

Remember: in Haskell a type is a set that has its possible values as elements in it.

참고URL : https://stackoverflow.com/questions/16892570/what-is-in-haskell-exactly

반응형