Fork me on GitHub
#clojure
<
2019-05-05
>
drone01:05:15

Type systems are becoming more expressive: https://youtu.be/4i7KrG1Afbk Dependent types allow for support of contract-like constraints. The use case of sub-selection for spec schemas can already be handled by structural types and row polymorphism, as in Typescript and Elm. If you’re comfortable with using spec for that particular use case, then you’d be comfortable using static types in Clojure.

didibus05:05:36

Can you talk about this more? What you mean by sub-selection? What is the use case for it, can you give an example?

drone22:05:01

check out Rich’s Maybe Not talk. he discusses having a spec schema from which you can select a subset of fields and provides motivation

didibus22:05:21

Okay, maybe I thought you were responding to me. That's why I got confused. But now I think you were talking to todo.

drone23:05:58

I just looked for a user named “todo”. smh

didibus23:05:38

I thought there was haha

drone23:05:21

oh man, so that wasn’t just a sprinkling of Spanish?

drone23:05:47

anyway, happy to provide pointers to any type-related info. I’ve been a long-time Clojure user and espouser of dynamic types. over past few years my opinion on types has changed based on experiences working on large Clojure and Python projects

drone23:05:40

I still use Clojure daily for work and hobbies, but starting to switch over to Haskell for some things

didibus23:05:51

If you were talking to me then, I was talking about full strictly static type systems. Not optional. Where every single construct needs to be typed. I still feel that doesn't work well with Lisps. Dependent types add a huge annotation burden which would defeat the Lisp dynamic interactive live programming flow, and make the sexpr syntax even more of an eye sore I feel. Hindley Milner typed can't type all of Lisps constructs, and if you changed Lisps so it could, it's no longer Lisp, but a weird sexpr like syntax for Haskell. The only types I think could work are simple systems like Java's type system. Common Lisp has support for those optionally for example and that works fine.

drone23:05:41

yeah, I agree with most of that and especially the optional portion. That’s the direction I wish Clojure was headed, optional/gradual typing

didibus23:05:52

I wasn't so much talking about being pro static or dynamic, but more that this holy grail everyone wants, which would be the dynamism of Lisp and the compiler checks of Haskell I think is a pipe dream, at least for now, because they literally oppose each other. What is great of Lisp is dynamism, which invariably means you can't have strong static guarantees. So you need to choose which one you prefer.

didibus23:05:11

I don't think optional typing work honestly

didibus23:05:27

That seem to have been an issue with core.type adoption

drone23:05:33

it’s kind of frustrating to see spec continue as a runtime-only checker when other languages can statically check the structural types that seem to be the main motivation behind spec

didibus23:05:05

They can't though

didibus23:05:17

Not without limiting the expressivity of the language

drone23:05:24

I think optional typing can work, I agree that core.typed didn’t get there

didibus23:05:49

You don't feel optional typing is a Jack of all trade master of none?

drone00:05:09

mm, so you can have specs that act as contracts. where one can use arbitrary predicates, and I agree those can’t be expressed in most type systems

didibus00:05:25

What good is it that X is typed, if the Y it calls or the G that calls it aren't

drone00:05:48

but look at Rich’s talk, and the specs he uses as examples can be statically checked. proof being Typescript, Elm, and OCaml

drone00:05:03

those borders are then checked via tests like spec does now

drone00:05:27

but regions of code that are fully annotated (or inferred) can be checked statically

didibus00:05:36

Hum... I don't remember the details, but his whole spiel was that Haskell and other strongly typed system did it wrong, and so did he with spec 1

drone00:05:07

yeah, he used a strawman where Maybe types were used to represent optional fields

didibus00:05:54

It was more of a criticism of being forced to define things where they aren't in the domain I felt.

didibus00:05:18

If you define an ADT, you need to specify all that could be in it.

drone00:05:29

Haskell does have the ability to express structural types, but it is a little bit hacky. The languages I already mentioned: Typescript, Elm, and OCaml; are statically typed and do have solid support for structural types and handle the issue Rich brought up

didibus00:05:52

Problem is, different time or states have different requirements of what could be in it.

drone00:05:59

yep, exactly

didibus00:05:02

How? I don't have experience with those

didibus00:05:21

Let's say I had a Customer with a Name and Credit Card

didibus00:05:43

Both are mandatory for some functions, but required for others?

didibus00:05:48

How you model that?

drone00:05:26

so the keywords to search for are “row polymorphism” and “structural types”, but as for how they work, it’s similar to what’s coming in spec2. You have some type schema/definition, and for a function that only requires a subset of the fields defined in the schema, the type/spec for the function parameter is defined as a subset of the schema

didibus00:05:13

Ya, I guess that was his insight. And Haskell doesn't support them first class, which was his criticism

didibus00:05:04

But doesn't both of these suffer if safety?

didibus00:05:08

For static systems

didibus00:05:26

How do they handle type equality or mismatch?

drone00:05:32

in Haskell, the closest reasonable solution is probably lenses. but that isn’t really row polymorphism. if you ask Haskell folks for three or so top issues to fix in the language, records/row types will be one of them

drone00:05:25

how do you mean?

drone00:05:13

there’s some info on row polymorphism in OCaml. maybe that helps? as to what the type checker is doing, I don’t know

didibus00:05:26

So, say you have a record of [:a :b :c]. And there's a function that needs [:a :c] and its given [:a :c :x :y] as input. And say it returns [:a :b :c], how does it know these are all compatible types?

drone00:05:16

because you only need [:a :c], and any additional fields are ignored

drone00:05:42

note that :a and :c will have types too, so the matching is based on field-type pairs

didibus00:05:55

Okay, the link you sent me mentions the limitations I'm talking about.

didibus00:05:53

In the "Subtyping Versus Row Polymorphism" section.

didibus00:05:52

They talk how each approach has trade offs, and neither can perfectly give us the behaviour we want, but that's the compromise of static type systems often time, you adapt to the way the type checker works in order to rake its benefits

didibus00:05:31

"For example, lists of heterogeneous elements cannot be created using row polymorphism"

didibus00:05:56

Like that's a pretty big blocker for Clojure and most Lisps. Since they're most primitive construct are heterogeneous lists

drone00:05:04

that’s also an implementation detail of the type checker for a specific language

didibus00:05:16

Hum... are you sure? They don't make it sound like it. It seems to be a limitation of row polymorphism. Subtyping can do it, but then things can't be type inferred and needs to be explicitly casted it seems.

drone00:05:40

I honestly don’t know enough about OCaml to discuss it. =/ Let me do some reading and see if there’s an example of a type system without that limitation

didibus00:05:40

I need to look more into both of them for sure. Seems interesting

didibus00:05:31

Thanks for pointing it out

drone00:05:21

thanks for discussing it, I wish that happened more here! it’s good to run into the limitations

didibus00:05:04

Ya, thanks to you too. I like both static and dynamic languages. For different reasons. I prefer using certain dynamic langs, but not all. Only Lisps really, otherwise I prefer statically typed languages. I'm more experienced with C++, Java, C#. But have learned Haskell amateurishly. In general though, I find every static system is restrictive in one or another way. That's inherent from the problem. How do you teach a computer to understand and reason about code in a way that it can catch the bugs in it, when you couldn't. Nobody would say no to that, if it didn't come with trade offs. And even between strategies for achieving this there are many trade offs. So I tend to be skeptic of the static type advocates that don't start with talking about limitations and sacrifices of their approach 😋

didibus00:05:27

That doesn't mean sometimes the trade isn't worth it. But you need to accept that you will model things, not necessarily the way that you want to model it, not necessarily the way that is most intuitive, and not necessarily the way that the domain has it, but the way the type checker needs it.

didibus00:05:40

At the end of the day, for me, it comes down to what I'd rather do and who I trust more. Could I personally catch more bugs if I was modeling it my way, and having fun doing it? Or would the type checker catch more bugs if I modeled it its way, and felt frustrated throughout the process 😋

didibus00:05:56

So in practice, I found I do better with Clojure over Java,C#,C++, but I do worse with Python, JS and Ruby.

didibus00:05:34

I guess though, I need to give Haskell, OCaml and PureScript a real good try to see if I end up doing better with them

drone00:05:03

Have to run to dinner, but definitely up for talking more about types and trade offs.

drone00:05:22

The biggest hurdle for me with Haskell was getting comfortable with the handling of Effects through types. OCaml or others that aren’t pure (i.e., monads or some other effects system isn’t required) are maybe a good place to start exploring

drone00:05:06

And Idris’ dependent types are really cool

didibus00:05:10

Have a good meal

theeternalpulse02:05:51

When doing a defrecord or type while implemening a protocol, does it matter if your implemented function use the "this" binding or the bound variables in the protocol definition?

theeternalpulse02:05:28

I often see them omitted as (some-protocol-fn [_ some-other-args] ..)

theeternalpulse03:05:57

I guess using this would give you the whole entity, for assoc or update.

Cora (she/her)03:05:57

does anyone here use some kind of coroutine library with clojure? I'm used to using M:N threading with things like Elixir and Go and the idea of possibly starving out OS threads by blocking actions concerns me

Cora (she/her)03:05:15

it might be a remote concern, but still a concern

lilactown03:05:54

core.async implements CSP with a thread pool

lilactown03:05:26

but you can still block the thread pool by doing blocking IO on a channel

dominicm07:05:31

Core async would work without a thread pool. It could be tuned to a single thread.

p-himik09:05:57

Cross-posting the underlying question from #cursive. I have a code that calls function with some not null arguments, but one of the arguments becomes null in the function's body. Here's a piece of code of interest, it's from cognitect.transit:

(defn write
  "Writes a value to a transit writer."
  [^Writer writer o]
  (.write ^com.cognitect.transit.Writer (.w writer) o))
During debugging, when the current line is the very first one, writer is not null. But as soon as I step into , it becomes null for no apparent reason. How can such thing possibly happen?

p-himik09:05:10

Nevermind that - I got confused by the inconsistently highlighted stacktrace, and the reported null value is not null in reality.

markx11:05:41

Hi! Is there a readline lib that I can use to build completion and history function on terminal input? Something similar to https://github.com/chzyer/readline

dominicm11:05:31

Jline probably

markx12:05:47

@dominicm No Clj lib available ? I found bhauman/rebel-readline, but couldn’t find a simple example of how to use it for my use case. It’s mostly documented for REPL use.

dominicm12:05:11

@markx none I've encountered

didibus16:05:30

@markx You shouldn't hesitate to use Java libs from Clojure. It's part of the power that Clojure brings to make that easy and possible. Rebel-readline uses JLine itself.

👍 4
lilactown18:05:40

how are people using tap> in practice?

seancorfield19:05:41

@lilactown For debugging, instead of "println" debugging.

seancorfield19:05:05

When I'm debugging, I add tap> calls (instead of prn/`println`), and eval the top-level block (to redef the function I just changed), and then run whatever scenario I need and see what crops up in REBL.

lilactown19:05:23

I know you use rebl right. Wondering if other people do the same but just wire it to prn

lilactown19:05:54

Slow typer on my iPad 😜

seancorfield19:05:20

Before using REBL, I used to connect an additional Socket REPL and telnet into it and add a tap watcher that prn's stuff.

seancorfield19:05:42

That way tap output goes to a separate terminal from my regular stdout.

lilactown19:05:51

Do you leave in tap after you commit?

seancorfield19:05:10

Not generally.

seancorfield19:05:34

While it's safe to leave it in, I don't see much point in having tap> calls in production code.

seancorfield19:05:29

If you need tap-debugging in production, you can always connect to the process, eval in new function definitions with tap>, do the debugging, and then eval in the original version again.

seancorfield19:05:11

(mind you, if you're running with direct linking enabled, that workflow doesn't always work, unless you re-eval further up the call chain).

holyjak20:05:20

Is there a way to pretty-print the output of macroexpand-1 to make it more readable than 1 long line? Thanks!

jumpnbrownweasel21:05:37

are you looking for clojure.pprint/pprint?

👍 4
❤️ 4