Iota is a ridiculously small "programming language" using only one combinator. I'm interested in understanding how it works, but it would be helpful to see the implementation in a language I'm familiar with.
I found an implementation of the Iota programming language written in Scheme. I've been having a little trouble translating it to Haskell though. It's rather simple, but I'm relatively new to both Haskell and Scheme.
How would you write an equivalent Iota implementation in Haskell?
(let iota ()
(if (eq? #\* (read-char)) ((iota)(iota))
(lambda (c) ((c (lambda (x) (lambda (y) (lambda (z) ((x z)(y z))))))
(lambda (x) (lambda (y) x))))))
I've been teaching myself some of this stuff, so I sure hope I get the following right...
As n.m. mentions, the fact that Haskell is typed is of enormous importance to this question; type systems restrict what expressions can be formed, and in particular the most basic type systems for the lambda calculus forbid self-application, which ends up giving you a non-Turing complete language. Turing completeness is added on top of the basic type system as an extra feature to the language (either a
fix :: (a -> a) -> a
operator or recursive types).This doesn't mean you can't implement this at all in Haskell, but rather that such an implementation is not going to have just one operator.
Approach #1: implement the second example one-point combinatory logic basis from here, and add a
fix
function:Now you can write any program in terms of
iota'
andfix
. Explaining how this works is a bit involved. (EDIT: note that thisiota'
is not the same as theλx.x S K
in the original question; it'sλx.x K S K
, which is also Turing-complete. It is the case thatiota'
programs are going to be different fromiota
programs. I've tried theiota = λx.x S K
definition in Haskell; it typechecks, but when you tryk = iota (iota (iota iota))
ands = iota (iota (iota (iota iota)))
you get type errors.)Approach #2: Untyped lambda calculus denotations can be embedded into Haskell using this recursive type:
D
is basically a type whose elements are functions fromD
toD
. We haveIn :: (D -> D) -> D
to convert aD -> D
function into a plainD
, andout :: D -> (D -> D)
to do the opposite. So if we havex :: D
, we can self-apply it by doingout x x :: D
.Give that, now we can write:
This requires some "noise" from the
In
andout
; Haskell still forbids you to apply aD
to aD
, but we can useIn
andout
to get around this. You can't actually do anything useful with values of typeD
, but you could design a useful type around the same pattern.EDIT: iota is basically
λx.x S K
, whereK = λx.λy.x
andS = λx.λy.λz.x z (y z)
. I.e., iota takes a two-argument function and applies it to S and K; so by passing a function that returns its first argument you get S, and by passing a function that returns its second argument you get K. So if you can write the "return first argument" and the "return second argument" with iota, you can write S and K with iota. But S and K are enough to get Turing completeness, so you also get Turing completeness in the bargain. It does turn out that you can write the requisite selector functions with iota, so iota is enough for Turing completeness.So this reduces the problem of understanding iota to understanding the SK calculus.